From bafe545da7d8e31c3eb5ca2703945f2714181a8d Mon Sep 17 00:00:00 2001 From: Alper Nebi Yasak Date: Sat, 25 Nov 2023 12:31:11 +0000 Subject: [PATCH] alsa-ucm: Disable UCM devices when suspending Disabling UCM devices might save some power, according to an earlier discussion [1]. Disable them when suspending sinks/sources, and enable them when unsuspending. However, doing only that much introduces problems. The hardware controls we track for volume and mute state can change as part of disabling the UCM device. Enabling it back does not restore it to its pre-suspend state, so the UCM-triggered changes to disable the device will show up on user interfaces and cause confusion. The volume/mute should not be kept in sync with hardware for inactive UCM devices [2]. Skip the callbacks for reading/changing volume/mute state if the UCM device is disabled. This way, the volume/mute controls for sinks/sources are essentially detached from the hardware controls until the UCM device is re-enabled. Finally, sync volume and mute state for the sinks/sources just after we re-enable the UCM devices, to restore things to the pre-suspend state. Combined with the above, this means we can still change volume/mute state in user interfaces while the sink/source is suspended, and its updated value will be applied to the UCM device when it's actually going to be used. [1] https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/294#note_522388 [2] https://gitlab.freedesktop.org/pulseaudio/pulseaudio/-/merge_requests/772#note_1872757 Co-developed-by: Tanu Kaskinen [Alper: Rebase, split enable/disable functions, skip volume/mute callbacks if disabled, sync mixer at unsuspend, edit message] Signed-off-by: Alper Nebi Yasak --- src/modules/alsa/alsa-sink.c | 46 ++++++++++++++++++++++++++++++++++ src/modules/alsa/alsa-source.c | 46 ++++++++++++++++++++++++++++++++++ src/modules/alsa/alsa-ucm.c | 24 ++++++++++++++++++ src/modules/alsa/alsa-ucm.h | 4 +++ 4 files changed, 120 insertions(+) diff --git a/src/modules/alsa/alsa-sink.c b/src/modules/alsa/alsa-sink.c index ca22f195f..898156435 100644 --- a/src/modules/alsa/alsa-sink.c +++ b/src/modules/alsa/alsa-sink.c @@ -195,6 +195,7 @@ enum { static void userdata_free(struct userdata *u); static int unsuspend(struct userdata *u, bool recovering); +static void sync_mixer(struct userdata *u, pa_device_port *port); /* FIXME: Is there a better way to do this than device names? */ static bool is_iec958(struct userdata *u) { @@ -1087,6 +1088,12 @@ static void suspend(struct userdata *u) { pa_sink_set_max_rewind_within_thread(u->sink, 0); pa_sink_set_max_request_within_thread(u->sink, 0); + /* Disabling the UCM devices may save some power. */ + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(u->sink->active_port); + pa_alsa_ucm_port_device_disable(data); + } + pa_log_info("Device suspended..."); } @@ -1202,6 +1209,13 @@ static int unsuspend(struct userdata *u, bool recovering) { pa_log_info("Trying resume..."); + /* We disable all UCM devices when suspending, so let's enable them again. */ + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(u->sink->active_port); + pa_alsa_ucm_port_device_enable(data); + sync_mixer(u, u->sink->active_port); + } + if ((is_iec958(u) || is_hdmi(u)) && pa_sink_is_passthrough(u->sink)) { /* Need to open device in NONAUDIO mode */ int len = strlen(u->device_name) + 8; @@ -1508,6 +1522,12 @@ static void sink_get_volume_cb(pa_sink *s) { pa_assert(u->mixer_path); pa_assert(u->mixer_handle); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) + return; + } + if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) return; @@ -1538,6 +1558,12 @@ static void sink_set_volume_cb(pa_sink *s) { pa_assert(u->mixer_path); pa_assert(u->mixer_handle); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) + return; + } + /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume); @@ -1601,6 +1627,12 @@ static void sink_write_volume_cb(pa_sink *s) { pa_assert(u->mixer_handle); pa_assert(s->flags & PA_SINK_DEFERRED_VOLUME); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) + return; + } + /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->base_volume); @@ -1639,6 +1671,14 @@ static int sink_get_mute_cb(pa_sink *s, bool *mute) { pa_assert(u->mixer_path); pa_assert(u->mixer_handle); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) { + *mute = s->muted; + return 0; + } + } + if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, mute) < 0) return -1; @@ -1652,6 +1692,12 @@ static void sink_set_mute_cb(pa_sink *s) { pa_assert(u->mixer_path); pa_assert(u->mixer_handle); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) + return; + } + pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted); } diff --git a/src/modules/alsa/alsa-source.c b/src/modules/alsa/alsa-source.c index d88c47f1f..b2b1d0185 100644 --- a/src/modules/alsa/alsa-source.c +++ b/src/modules/alsa/alsa-source.c @@ -176,6 +176,7 @@ enum { static void userdata_free(struct userdata *u); static int unsuspend(struct userdata *u, bool recovering); +static void sync_mixer(struct userdata *u, pa_device_port *port); static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct userdata *u) { pa_assert(r); @@ -995,6 +996,12 @@ static void suspend(struct userdata *u) { /* Close PCM device */ close_pcm(u); + /* Disabling the UCM devices may save some power. */ + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(u->source->active_port); + pa_alsa_ucm_port_device_disable(data); + } + pa_log_info("Device suspended..."); } @@ -1085,6 +1092,13 @@ static int unsuspend(struct userdata *u, bool recovering) { pa_log_info("Trying resume..."); + /* We disable all UCM devices when suspending, so let's enable them again. */ + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(u->source->active_port); + pa_alsa_ucm_port_device_enable(data); + sync_mixer(u, u->source->active_port); + } + /* * On some machines, during the system suspend and resume, the thread_func could receive * POLLERR events before the dev nodes in /dev/snd/ are accessible, and thread_func calls @@ -1382,6 +1396,12 @@ static void source_get_volume_cb(pa_source *s) { pa_assert(u->mixer_path); pa_assert(u->mixer_handle); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) + return; + } + if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0) return; @@ -1412,6 +1432,12 @@ static void source_set_volume_cb(pa_source *s) { pa_assert(u->mixer_path); pa_assert(u->mixer_handle); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) + return; + } + /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->base_volume); @@ -1475,6 +1501,12 @@ static void source_write_volume_cb(pa_source *s) { pa_assert(u->mixer_handle); pa_assert(s->flags & PA_SOURCE_DEFERRED_VOLUME); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) + return; + } + /* Shift up by the base volume */ pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->base_volume); @@ -1513,6 +1545,14 @@ static int source_get_mute_cb(pa_source *s, bool *mute) { pa_assert(u->mixer_path); pa_assert(u->mixer_handle); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) { + *mute = s->muted; + return 0; + } + } + if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, mute) < 0) return -1; @@ -1526,6 +1566,12 @@ static void source_set_mute_cb(pa_source *s) { pa_assert(u->mixer_path); pa_assert(u->mixer_handle); + if (u->ucm_context) { + pa_alsa_ucm_port_data *data = PA_DEVICE_PORT_DATA(s->active_port); + if (pa_alsa_ucm_port_device_status(data) <= 0) + return; + } + pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted); } diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c index 018c01739..c3c7cf0e3 100644 --- a/src/modules/alsa/alsa-ucm.c +++ b/src/modules/alsa/alsa-ucm.c @@ -2535,6 +2535,18 @@ static void ucm_port_data_free(pa_device_port *port) { pa_xfree(ucm_port->eld_mixer_device_name); } +int pa_alsa_ucm_port_device_enable(pa_alsa_ucm_port_data *data) { + return ucm_device_enable(data->ucm, data->device); +} + +int pa_alsa_ucm_port_device_disable(pa_alsa_ucm_port_data *data) { + return ucm_device_disable(data->ucm, data->device); +} + +long pa_alsa_ucm_port_device_status(pa_alsa_ucm_port_data *data) { + return ucm_device_status(data->ucm, data->device); +} + #else /* HAVE_ALSA_UCM */ /* Dummy functions for systems without UCM support */ @@ -2591,4 +2603,16 @@ void pa_alsa_ucm_roled_stream_begin(pa_alsa_ucm_config *ucm, const char *role, p void pa_alsa_ucm_roled_stream_end(pa_alsa_ucm_config *ucm, const char *role, pa_direction_t dir) { } +int pa_alsa_ucm_port_device_enable(pa_alsa_ucm_port_data *data) { + return -1; +} + +int pa_alsa_ucm_port_device_disable(pa_alsa_ucm_port_data *data) { + return -1; +} + +long pa_alsa_ucm_port_device_status(pa_alsa_ucm_port_data *data) { + return -1; +} + #endif diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h index 543eefe0f..dced5f8ea 100644 --- a/src/modules/alsa/alsa-ucm.h +++ b/src/modules/alsa/alsa-ucm.h @@ -286,6 +286,10 @@ struct pa_alsa_ucm_port_data { int eld_device; /* PCM device number */ }; +int pa_alsa_ucm_port_device_enable(pa_alsa_ucm_port_data *data); +int pa_alsa_ucm_port_device_disable(pa_alsa_ucm_port_data *data); +long pa_alsa_ucm_port_device_status(pa_alsa_ucm_port_data *data); + struct pa_alsa_ucm_volume { char *mixer_elem; /* mixer element identifier */ char *master_elem; /* master mixer element identifier */