summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTanu Kaskinen <tanu.kaskinen@linux.intel.com>2015-05-04 21:03:45 +0300
committerTanu Kaskinen <tanu.kaskinen@linux.intel.com>2015-08-21 14:33:11 +0300
commit38e81f4c3dd1751fa78480daee3c71b73b45f7a7 (patch)
tree6d870d984c3e9d302339ded70ec93ac460715701
parentd2bed5332ab6e25072d87f43f418feb1c93c080d (diff)
ucm: Add support for "JackHWMute"
JackHWMute is used to list devices that get forcibly muted by a particular jack. We mark ports unavailable based on that information.
-rw-r--r--src/modules/alsa/alsa-mixer.c52
-rw-r--r--src/modules/alsa/alsa-mixer.h2
-rw-r--r--src/modules/alsa/alsa-ucm.c76
-rw-r--r--src/modules/alsa/alsa-ucm.h4
4 files changed, 134 insertions, 0 deletions
diff --git a/src/modules/alsa/alsa-mixer.c b/src/modules/alsa/alsa-mixer.c
index c6d6212b9..a0b2493b7 100644
--- a/src/modules/alsa/alsa-mixer.c
+++ b/src/modules/alsa/alsa-mixer.c
@@ -119,6 +119,7 @@ pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) {
jack->state_unplugged = PA_AVAILABLE_NO;
jack->state_plugged = PA_AVAILABLE_YES;
jack->ucm_devices = pa_dynarray_new(NULL);
+ jack->ucm_hw_mute_devices = pa_dynarray_new(NULL);
return jack;
}
@@ -126,6 +127,9 @@ pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name) {
void pa_alsa_jack_free(pa_alsa_jack *jack) {
pa_assert(jack);
+ if (jack->ucm_hw_mute_devices)
+ pa_dynarray_free(jack->ucm_hw_mute_devices);
+
if (jack->ucm_devices)
pa_dynarray_free(jack->ucm_devices);
@@ -145,6 +149,9 @@ void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control) {
jack->has_control = has_control;
+ PA_DYNARRAY_FOREACH(device, jack->ucm_hw_mute_devices, idx)
+ pa_alsa_ucm_device_update_available(device);
+
PA_DYNARRAY_FOREACH(device, jack->ucm_devices, idx)
pa_alsa_ucm_device_update_available(device);
}
@@ -160,6 +167,44 @@ void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in) {
jack->plugged_in = plugged_in;
+ /* XXX: If this is a headphone jack that mutes speakers when plugged in,
+ * and the headphones get unplugged, then the headphone device must be set
+ * to unavailable and the speaker device must be set to unknown. So far so
+ * good. But there's an ugly detail: we must first set the availability of
+ * the speakers and then the headphones. We shouldn't need to care about
+ * the order, but we have to, because module-switch-on-port-available gets
+ * separate events for the two devices, and the intermediate state between
+ * the two events is such that the second event doesn't trigger the desired
+ * port switch, if the event order is "wrong".
+ *
+ * These are the transitions when the event order is "right":
+ *
+ * speakers: 1) unavailable -> 2) unknown -> 3) unknown
+ * headphones: 1) available -> 2) available -> 3) unavailable
+ *
+ * In the 2 -> 3 transition, headphones become unavailable, and
+ * module-switch-on-port-available sees that speakers can be used, so the
+ * port gets changed as it should.
+ *
+ * These are the transitions when the event order is "wrong":
+ *
+ * speakers: 1) unavailable -> 2) unavailable -> 3) unknown
+ * headphones: 1) available -> 2) unavailable -> 3) unavailable
+ *
+ * In the 1 -> 2 transition, headphones become unavailable, and there are
+ * no available ports to use, so no port change happens. In the 2 -> 3
+ * transition, speaker availability becomes unknown, but that's not
+ * a strong enough signal for module-switch-on-port-available, so it still
+ * doesn't do the port switch.
+ *
+ * We should somehow merge the two events so that
+ * module-switch-on-port-available would handle both transitions in one go.
+ * If module-switch-on-port-available used a defer event to delay
+ * the port availability processing, that would probably do the trick. */
+
+ PA_DYNARRAY_FOREACH(device, jack->ucm_hw_mute_devices, idx)
+ pa_alsa_ucm_device_update_available(device);
+
PA_DYNARRAY_FOREACH(device, jack->ucm_devices, idx)
pa_alsa_ucm_device_update_available(device);
}
@@ -171,6 +216,13 @@ void pa_alsa_jack_add_ucm_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device)
pa_dynarray_append(jack->ucm_devices, device);
}
+void pa_alsa_jack_add_ucm_hw_mute_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device) {
+ pa_assert(jack);
+ pa_assert(device);
+
+ pa_dynarray_append(jack->ucm_hw_mute_devices, device);
+}
+
static const char *lookup_description(const char *key, const struct description_map dm[], unsigned n) {
unsigned i;
diff --git a/src/modules/alsa/alsa-mixer.h b/src/modules/alsa/alsa-mixer.h
index 96a7a5ee0..621a71bde 100644
--- a/src/modules/alsa/alsa-mixer.h
+++ b/src/modules/alsa/alsa-mixer.h
@@ -170,6 +170,7 @@ struct pa_alsa_jack {
pa_alsa_required_t required_absent;
pa_dynarray *ucm_devices; /* pa_alsa_ucm_device */
+ pa_dynarray *ucm_hw_mute_devices; /* pa_alsa_ucm_device */
};
pa_alsa_jack *pa_alsa_jack_new(pa_alsa_path *path, const char *name);
@@ -177,6 +178,7 @@ void pa_alsa_jack_free(pa_alsa_jack *jack);
void pa_alsa_jack_set_has_control(pa_alsa_jack *jack, bool has_control);
void pa_alsa_jack_set_plugged_in(pa_alsa_jack *jack, bool plugged_in);
void pa_alsa_jack_add_ucm_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device);
+void pa_alsa_jack_add_ucm_hw_mute_device(pa_alsa_jack *jack, pa_alsa_ucm_device *device);
/* A path wraps a series of elements into a single entity which can be
* used to control it as if it had a single volume slider, a single
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index d2c714081..c231bb436 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -77,6 +77,9 @@ struct ucm_info {
};
static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack);
+static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack);
+
+static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name);
struct ucm_port {
pa_alsa_ucm_config *ucm;
@@ -107,6 +110,7 @@ static struct ucm_items item[] = {
{"CaptureChannels", PA_ALSA_PROP_UCM_CAPTURE_CHANNELS},
{"TQ", PA_ALSA_PROP_UCM_QOS},
{"JackControl", PA_ALSA_PROP_UCM_JACK_CONTROL},
+ {"JackHWMute", PA_ALSA_PROP_UCM_JACK_HW_MUTE},
{NULL, NULL},
};
@@ -414,6 +418,7 @@ static int ucm_get_devices(pa_alsa_ucm_verb *verb, snd_use_case_mgr_t *uc_mgr) {
pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_NAME, pa_strnull(dev_list[i]));
pa_proplist_sets(d->proplist, PA_ALSA_PROP_UCM_DESCRIPTION, pa_strna(dev_list[i + 1]));
d->ucm_ports = pa_dynarray_new(NULL);
+ d->hw_mute_jacks = pa_dynarray_new(NULL);
d->available = PA_AVAILABLE_UNKNOWN;
PA_LLIST_PREPEND(pa_alsa_ucm_device, verb->devices, d);
@@ -1381,6 +1386,7 @@ static int ucm_create_profile(
PA_LLIST_FOREACH(dev, verb->devices) {
pa_alsa_jack *jack;
+ const char *jack_hw_mute;
name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
@@ -1391,6 +1397,37 @@ static int ucm_create_profile(
jack = ucm_get_jack(ucm, dev);
device_set_jack(dev, jack);
+
+ /* JackHWMute contains a list of device names. Each listed device must
+ * be associated with the jack object that we just created. */
+ jack_hw_mute = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_JACK_HW_MUTE);
+ if (jack_hw_mute) {
+ char *hw_mute_device_name;
+ const char *state = NULL;
+
+ while ((hw_mute_device_name = pa_split_spaces(jack_hw_mute, &state))) {
+ pa_alsa_ucm_verb *verb2;
+ bool device_found = false;
+
+ /* Search the referenced device from all verbs. If there are
+ * multiple verbs that have a device with this name, we add the
+ * hw mute association to each of those devices. */
+ PA_LLIST_FOREACH(verb2, ucm->verbs) {
+ pa_alsa_ucm_device *hw_mute_device;
+
+ hw_mute_device = verb_find_device(verb2, hw_mute_device_name);
+ if (hw_mute_device) {
+ device_found = true;
+ device_add_hw_mute_jack(hw_mute_device, jack);
+ }
+ }
+
+ if (!device_found)
+ pa_log("[%s] JackHWMute references an unknown device: %s", name, hw_mute_device_name);
+
+ pa_xfree(hw_mute_device_name);
+ }
+ }
}
/* Now find modifiers that have their own PlaybackPCM and create
@@ -1596,6 +1633,9 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
PA_LLIST_FOREACH_SAFE(di, dn, verb->devices) {
PA_LLIST_REMOVE(pa_alsa_ucm_device, verb->devices, di);
+ if (di->hw_mute_jacks)
+ pa_dynarray_free(di->hw_mute_jacks);
+
if (di->ucm_ports)
pa_dynarray_free(di->ucm_ports);
@@ -1621,6 +1661,23 @@ static void free_verb(pa_alsa_ucm_verb *verb) {
pa_xfree(verb);
}
+static pa_alsa_ucm_device *verb_find_device(pa_alsa_ucm_verb *verb, const char *device_name) {
+ pa_alsa_ucm_device *device;
+
+ pa_assert(verb);
+ pa_assert(device_name);
+
+ PA_LLIST_FOREACH(device, verb->devices) {
+ const char *name;
+
+ name = pa_proplist_gets(device->proplist, PA_ALSA_PROP_UCM_NAME);
+ if (pa_streq(name, device_name))
+ return device;
+ }
+
+ return NULL;
+}
+
void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm) {
pa_alsa_ucm_verb *vi, *vn;
pa_alsa_jack *ji, *jn;
@@ -1737,6 +1794,16 @@ static void device_set_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
pa_alsa_ucm_device_update_available(device);
}
+static void device_add_hw_mute_jack(pa_alsa_ucm_device *device, pa_alsa_jack *jack) {
+ pa_assert(device);
+ pa_assert(jack);
+
+ pa_dynarray_append(device->hw_mute_jacks, jack);
+ pa_alsa_jack_add_ucm_hw_mute_device(jack, device);
+
+ pa_alsa_ucm_device_update_available(device);
+}
+
static void device_set_available(pa_alsa_ucm_device *device, pa_available_t available) {
struct ucm_port *port;
unsigned idx;
@@ -1754,12 +1821,21 @@ static void device_set_available(pa_alsa_ucm_device *device, pa_available_t avai
void pa_alsa_ucm_device_update_available(pa_alsa_ucm_device *device) {
pa_available_t available = PA_AVAILABLE_UNKNOWN;
+ pa_alsa_jack *jack;
+ unsigned idx;
pa_assert(device);
if (device->jack && device->jack->has_control)
available = device->jack->plugged_in ? PA_AVAILABLE_YES : PA_AVAILABLE_NO;
+ PA_DYNARRAY_FOREACH(jack, device->hw_mute_jacks, idx) {
+ if (jack->plugged_in) {
+ available = PA_AVAILABLE_NO;
+ break;
+ }
+ }
+
device_set_available(device, available);
}
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index 5ccc4663b..093030350 100644
--- a/src/modules/alsa/alsa-ucm.h
+++ b/src/modules/alsa/alsa-ucm.h
@@ -87,6 +87,9 @@ typedef void snd_use_case_mgr_t;
/* Corresponds to the "JackControl" UCM value. */
#define PA_ALSA_PROP_UCM_JACK_CONTROL "alsa.ucm.jack_control"
+/* Corresponds to the "JackHWMute" UCM value. */
+#define PA_ALSA_PROP_UCM_JACK_HW_MUTE "alsa.ucm.jack_hw_mute"
+
typedef struct pa_alsa_ucm_verb pa_alsa_ucm_verb;
typedef struct pa_alsa_ucm_modifier pa_alsa_ucm_modifier;
typedef struct pa_alsa_ucm_device pa_alsa_ucm_device;
@@ -148,6 +151,7 @@ struct pa_alsa_ucm_device {
pa_dynarray *ucm_ports; /* struct ucm_port */
pa_alsa_jack *jack;
+ pa_dynarray *hw_mute_jacks; /* pa_alsa_jack */
pa_available_t available;
};