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 <tanuk@iki.fi>
[Alper: Rebase, split enable/disable functions, skip volume/mute
        callbacks if disabled, sync mixer at unsuspend, edit message]
Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
This commit is contained in:
Alper Nebi Yasak 2023-11-25 12:31:11 +00:00
parent 3e2bb8a1ec
commit bafe545da7
4 changed files with 120 additions and 0 deletions

View file

@ -195,6 +195,7 @@ enum {
static void userdata_free(struct userdata *u); static void userdata_free(struct userdata *u);
static int unsuspend(struct userdata *u, bool recovering); 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? */ /* FIXME: Is there a better way to do this than device names? */
static bool is_iec958(struct userdata *u) { 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_rewind_within_thread(u->sink, 0);
pa_sink_set_max_request_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..."); pa_log_info("Device suspended...");
} }
@ -1202,6 +1209,13 @@ static int unsuspend(struct userdata *u, bool recovering) {
pa_log_info("Trying resume..."); 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)) { if ((is_iec958(u) || is_hdmi(u)) && pa_sink_is_passthrough(u->sink)) {
/* Need to open device in NONAUDIO mode */ /* Need to open device in NONAUDIO mode */
int len = strlen(u->device_name) + 8; 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_path);
pa_assert(u->mixer_handle); 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) if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
return; return;
@ -1538,6 +1558,12 @@ static void sink_set_volume_cb(pa_sink *s) {
pa_assert(u->mixer_path); pa_assert(u->mixer_path);
pa_assert(u->mixer_handle); 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 */ /* Shift up by the base volume */
pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->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(u->mixer_handle);
pa_assert(s->flags & PA_SINK_DEFERRED_VOLUME); 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 */ /* Shift up by the base volume */
pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->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_path);
pa_assert(u->mixer_handle); 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) if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, mute) < 0)
return -1; return -1;
@ -1652,6 +1692,12 @@ static void sink_set_mute_cb(pa_sink *s) {
pa_assert(u->mixer_path); pa_assert(u->mixer_path);
pa_assert(u->mixer_handle); 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); pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted);
} }

View file

@ -176,6 +176,7 @@ enum {
static void userdata_free(struct userdata *u); static void userdata_free(struct userdata *u);
static int unsuspend(struct userdata *u, bool recovering); 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) { static pa_hook_result_t reserve_cb(pa_reserve_wrapper *r, void *forced, struct userdata *u) {
pa_assert(r); pa_assert(r);
@ -995,6 +996,12 @@ static void suspend(struct userdata *u) {
/* Close PCM device */ /* Close PCM device */
close_pcm(u); 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..."); pa_log_info("Device suspended...");
} }
@ -1085,6 +1092,13 @@ static int unsuspend(struct userdata *u, bool recovering) {
pa_log_info("Trying resume..."); 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 * 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 * 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_path);
pa_assert(u->mixer_handle); 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) if (pa_alsa_path_get_volume(u->mixer_path, u->mixer_handle, &s->channel_map, &r) < 0)
return; return;
@ -1412,6 +1432,12 @@ static void source_set_volume_cb(pa_source *s) {
pa_assert(u->mixer_path); pa_assert(u->mixer_path);
pa_assert(u->mixer_handle); 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 */ /* Shift up by the base volume */
pa_sw_cvolume_divide_scalar(&r, &s->real_volume, s->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(u->mixer_handle);
pa_assert(s->flags & PA_SOURCE_DEFERRED_VOLUME); 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 */ /* Shift up by the base volume */
pa_sw_cvolume_divide_scalar(&hw_vol, &hw_vol, s->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_path);
pa_assert(u->mixer_handle); 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) if (pa_alsa_path_get_mute(u->mixer_path, u->mixer_handle, mute) < 0)
return -1; return -1;
@ -1526,6 +1566,12 @@ static void source_set_mute_cb(pa_source *s) {
pa_assert(u->mixer_path); pa_assert(u->mixer_path);
pa_assert(u->mixer_handle); 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); pa_alsa_path_set_mute(u->mixer_path, u->mixer_handle, s->muted);
} }

View file

@ -2535,6 +2535,18 @@ static void ucm_port_data_free(pa_device_port *port) {
pa_xfree(ucm_port->eld_mixer_device_name); 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 */ #else /* HAVE_ALSA_UCM */
/* Dummy functions for systems without UCM support */ /* 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) { 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 #endif

View file

@ -286,6 +286,10 @@ struct pa_alsa_ucm_port_data {
int eld_device; /* PCM device number */ 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 { struct pa_alsa_ucm_volume {
char *mixer_elem; /* mixer element identifier */ char *mixer_elem; /* mixer element identifier */
char *master_elem; /* master mixer element identifier */ char *master_elem; /* master mixer element identifier */