[pulseaudio-discuss] [PATCH 1/4] alsa: Add extcon (Android switch) jack detection

David Henningsson david.henningsson at canonical.com
Wed Sep 18 07:01:49 PDT 2013


This is for headphone/headset only, so far. It currently works for
Android switches, but should be easy to convert to extcon when there
is a need, hence the name.

Meanwhile, if you're the 99% that does not run an Android kernel with
this switch, this patch should be harmless - it would just notice there
is no switch and don't do anything else.
---
 src/Makefile.am                     |    1 +
 src/modules/alsa/alsa-extcon.c      |  272 +++++++++++++++++++++++++++++++++++
 src/modules/alsa/alsa-extcon.h      |   33 +++++
 src/modules/alsa/alsa-ucm.c         |    4 +-
 src/modules/alsa/alsa-ucm.h         |    1 +
 src/modules/alsa/module-alsa-card.c |    5 +
 6 files changed, 314 insertions(+), 2 deletions(-)
 create mode 100644 src/modules/alsa/alsa-extcon.c
 create mode 100644 src/modules/alsa/alsa-extcon.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 8392953..7caa1db 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -1729,6 +1729,7 @@ module_coreaudio_device_la_LIBADD = $(MODULE_LIBADD)
 libalsa_util_la_SOURCES = \
 		modules/alsa/alsa-util.c modules/alsa/alsa-util.h \
 		modules/alsa/alsa-ucm.c modules/alsa/alsa-ucm.h \
+		modules/alsa/alsa-extcon.c modules/alsa/alsa-extcon.h \
 		modules/alsa/alsa-mixer.c modules/alsa/alsa-mixer.h \
 		modules/alsa/alsa-sink.c modules/alsa/alsa-sink.h \
 		modules/alsa/alsa-source.c modules/alsa/alsa-source.h \
diff --git a/src/modules/alsa/alsa-extcon.c b/src/modules/alsa/alsa-extcon.c
new file mode 100644
index 0000000..fb21fdb
--- /dev/null
+++ b/src/modules/alsa/alsa-extcon.c
@@ -0,0 +1,272 @@
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 David Henningsson, Canonical Ltd.
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pulsecore/core-util.h>
+#include <pulsecore/device-port.h>
+#include <pulsecore/i18n.h>
+#include <libudev.h>
+
+#include "alsa-util.h"
+#include "alsa-extcon.h"
+
+/* IFDEF HAVE_UCM ? */
+#include <use-case.h>
+#include "alsa-ucm.h"
+/* ENDIF */
+
+/* For android */
+#define EXTCON_NAME "switch"
+/* For extcon */
+/* #define EXTCON_NAME "extcon" */
+
+static pa_available_t hp_avail(int state)
+{
+    return ((state & 3) != 0) ? PA_AVAILABLE_YES : PA_AVAILABLE_NO;
+}
+
+static pa_available_t hsmic_avail(int state)
+{
+    return (state & 1) ? PA_AVAILABLE_YES : PA_AVAILABLE_NO;
+}
+
+struct android_switch {
+    char *name;
+    uint32_t current_value;
+};
+
+static void android_switch_free(struct android_switch *as) {
+    if (!as)
+        return;
+    pa_xfree(as->name);
+    pa_xfree(as);
+}
+
+static struct android_switch *android_switch_new(const char *name) {
+
+    struct android_switch *as = NULL;
+    char *filename = pa_sprintf_malloc("/sys/class/%s/%s/state", EXTCON_NAME, name);
+    char *state = pa_read_line_from_file(filename);
+
+    if (state == NULL) {
+        pa_log_debug("Cannot open '%s'. Skipping.", filename);
+        pa_xfree(filename);
+        return NULL;
+    }
+    pa_xfree(filename);
+
+    as = pa_xnew0(struct android_switch, 1);
+    as->name = pa_xstrdup(name);
+
+    if (pa_atou(state, &as->current_value) < 0) {
+        pa_log_warn("Switch '%s' has invalid value '%s'", name, state);
+        pa_xfree(state);
+        android_switch_free(as);
+        return NULL;
+    }
+
+    return as;
+}
+
+struct udev_data {
+    struct udev *udev;
+    struct udev_monitor *monitor;
+    pa_io_event *event;
+};
+
+struct pa_alsa_extcon {
+    pa_card *card;
+    struct android_switch *h2w;
+    struct udev_data udev;
+};
+
+static struct android_switch *find_matching_switch(pa_alsa_extcon *u,
+                                                   const char *devpath) {
+
+    if (pa_streq(devpath, "/devices/virtual/" EXTCON_NAME "/h2w"))
+        return u->h2w;  /* To be extended if we ever support more switches */
+    return NULL;
+}
+
+static void notify_ports(pa_alsa_extcon *u, struct android_switch *as) {
+
+    pa_device_port *p;
+    void *state;
+
+    pa_assert(as == u->h2w); /* To be extended if we ever support more switches */
+
+    pa_log_debug("Value of switch %s is now %d.", as->name, as->current_value);
+
+    PA_HASHMAP_FOREACH(p, u->card->ports, state) {
+        if (p->direction == PA_DIRECTION_OUTPUT) {
+            if (!strcmp(p->name, "analog-output-headphones"))
+                 pa_device_port_set_available(p, hp_avail(as->current_value));
+/* IFDEF HAVE_UCM ? */
+            else if (pa_alsa_ucm_port_contains(p->name, SND_USE_CASE_DEV_HEADSET, true) ||
+                     pa_alsa_ucm_port_contains(p->name, SND_USE_CASE_DEV_HEADPHONES, true))
+                pa_device_port_set_available(p, hp_avail(as->current_value));
+/* ENDIF */
+        }
+        else if (p->direction == PA_DIRECTION_INPUT) {
+            if (!strcmp(p->name, "analog-input-headset-mic"))
+                pa_device_port_set_available(p, hsmic_avail(as->current_value));
+/* IFDEF HAVE_UCM ? */
+            else if (pa_alsa_ucm_port_contains(p->name, SND_USE_CASE_DEV_HEADSET, false))
+                pa_device_port_set_available(p, hsmic_avail(as->current_value));
+/* ENDIF */
+        }
+    }
+}
+
+static void udev_cb(pa_mainloop_api *a, pa_io_event *e, int fd,
+                    pa_io_event_flags_t events, void *userdata) {
+
+    pa_alsa_extcon *u = userdata;
+    struct udev_device *d = udev_monitor_receive_device(u->udev.monitor);
+    struct udev_list_entry *entry;
+    struct android_switch *as;
+    const char *devpath, *state;
+
+    if (!d) {
+        pa_log("udev_monitor_receive_device failed.");
+        pa_assert(a);
+        a->io_free(u->udev.event);
+        u->udev.event = NULL;
+        return;
+    }
+
+    devpath = udev_device_get_devpath(d);
+    if (!devpath) {
+        pa_log("udev_device_get_devpath failed.");
+        goto out;
+    }
+    pa_log_debug("Got uevent with devpath=%s", devpath);
+
+    as = find_matching_switch(u, devpath);
+    if (!as)
+        goto out;
+
+    entry = udev_list_entry_get_by_name(
+            udev_device_get_properties_list_entry(d), "SWITCH_STATE");
+    if (!entry) {
+        pa_log("udev_list_entry_get_by_name failed to find 'SWITCH_STATE' entry.");
+        goto out;
+    }
+
+    state = udev_list_entry_get_value(entry);
+    if (!state) {
+        pa_log("udev_list_entry_get_by_name failed.");
+        goto out;
+    }
+
+    if (pa_atou(state, &as->current_value) < 0) {
+        pa_log_warn("Switch '%s' has invalid value '%s'", as->name, state);
+        goto out;
+    }
+
+    notify_ports(u, as);
+
+out:
+    udev_device_unref(d);
+}
+
+static bool init_udev(pa_alsa_extcon *u, pa_core *core) {
+
+    int fd;
+
+    u->udev.udev = udev_new();
+    if (!u->udev.udev) {
+        pa_log("udev_new failed.");
+        return false;
+    }
+
+    u->udev.monitor = udev_monitor_new_from_netlink(u->udev.udev, "udev");
+    if (!u->udev.monitor) {
+        pa_log("udev_monitor_new_from_netlink failed.");
+        return false;
+    }
+/* FIXME: Research why this filter did not work */
+/*
+    if (udev_monitor_filter_add_match_subsystem_devtype(u->udev.monitor, EXTCON_NAME, NULL) < 0) {
+        pa_log("udev_monitor_filter_add_match_subsystem_devtype failed.");
+        return false;
+    }
+*/
+    if (udev_monitor_enable_receiving(u->udev.monitor) < 0) {
+        pa_log("udev_monitor_enable_receiving failed.");
+        return false;
+    }
+
+    fd = udev_monitor_get_fd(u->udev.monitor);
+    if (fd < 0) {
+        pa_log("udev_monitor_get_fd failed");
+        return false;
+    }
+
+    pa_assert_se(u->udev.event = core->mainloop->io_new(core->mainloop, fd,
+                 PA_IO_EVENT_INPUT, udev_cb, u));
+
+    return true;
+}
+
+pa_alsa_extcon *pa_alsa_extcon_new(pa_core *core, pa_card *card) {
+
+    pa_alsa_extcon *u = pa_xnew0(pa_alsa_extcon, 1);
+
+    pa_assert(core);
+    pa_assert(card);
+    u->card = card;
+    u->h2w = android_switch_new("h2w");
+    if (!u->h2w)
+        goto fail;
+
+    if (!init_udev(u, core))
+        goto fail;
+
+    notify_ports(u, u->h2w);
+    return u;
+
+fail:
+    pa_alsa_extcon_free(u);
+    return NULL;
+}
+
+void pa_alsa_extcon_free(pa_alsa_extcon *u) {
+
+    pa_assert(u);
+
+    if (u->udev.event)
+        u->card->core->mainloop->io_free(u->udev.event);
+
+    if (u->udev.monitor)
+        udev_monitor_unref(u->udev.monitor);
+
+    if (u->udev.udev)
+        udev_unref(u->udev.udev);
+
+    if (u->h2w)
+        android_switch_free(u->h2w);
+
+    pa_xfree(u);
+}
diff --git a/src/modules/alsa/alsa-extcon.h b/src/modules/alsa/alsa-extcon.h
new file mode 100644
index 0000000..7655746
--- /dev/null
+++ b/src/modules/alsa/alsa-extcon.h
@@ -0,0 +1,33 @@
+#ifndef fooalsaextconhfoo
+#define fooalsaextconhfoo
+
+/***
+  This file is part of PulseAudio.
+
+  Copyright 2013 David Henningsson, Canonical Ltd.
+
+  PulseAudio is free software; you can redistribute it and/or modify
+  it under the terms of the GNU Lesser General Public License as published
+  by the Free Software Foundation; either version 2.1 of the License,
+  or (at your option) any later version.
+
+  PulseAudio is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with PulseAudio; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA.
+***/
+
+/* TODO: Handle !HAVE_UDEV and !HAVE_UCM */
+
+typedef struct pa_alsa_extcon pa_alsa_extcon;
+
+pa_alsa_extcon *pa_alsa_extcon_new(pa_core *, pa_card *);
+
+void pa_alsa_extcon_free(pa_alsa_extcon *);
+
+#endif
diff --git a/src/modules/alsa/alsa-ucm.c b/src/modules/alsa/alsa-ucm.c
index 47ff926..81d0aeb 100644
--- a/src/modules/alsa/alsa-ucm.c
+++ b/src/modules/alsa/alsa-ucm.c
@@ -771,7 +771,7 @@ static void ucm_add_port_combination(
     }
 }
 
-static int ucm_port_contains(const char *port_name, const char *dev_name, bool is_sink) {
+int pa_alsa_ucm_port_contains(const char *port_name, const char *dev_name, bool is_sink) {
     int ret = 0;
     const char *r;
     const char *state = NULL;
@@ -1025,7 +1025,7 @@ int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *p
     PA_IDXSET_FOREACH(dev, context->ucm_devices, idx) {
         const char *dev_name = pa_proplist_gets(dev->proplist, PA_ALSA_PROP_UCM_NAME);
 
-        if (ucm_port_contains(port->name, dev_name, is_sink))
+        if (pa_alsa_ucm_port_contains(port->name, dev_name, is_sink))
             enable_devs[enable_num++] = dev_name;
         else {
             pa_log_debug("Disable ucm device %s", dev_name);
diff --git a/src/modules/alsa/alsa-ucm.h b/src/modules/alsa/alsa-ucm.h
index 2fae6c4..5e2f516 100644
--- a/src/modules/alsa/alsa-ucm.h
+++ b/src/modules/alsa/alsa-ucm.h
@@ -112,6 +112,7 @@ void pa_alsa_ucm_add_ports_combination(
         pa_card_profile *cp,
         pa_core *core);
 int pa_alsa_ucm_set_port(pa_alsa_ucm_mapping_context *context, pa_device_port *port, bool is_sink);
+int pa_alsa_ucm_port_contains(const char *port_name, const char *dev_name, bool is_sink);
 
 void pa_alsa_ucm_free(pa_alsa_ucm_config *ucm);
 void pa_alsa_ucm_mapping_context_free(pa_alsa_ucm_mapping_context *context);
diff --git a/src/modules/alsa/module-alsa-card.c b/src/modules/alsa/module-alsa-card.c
index 96e88e5..3a9c457 100644
--- a/src/modules/alsa/module-alsa-card.c
+++ b/src/modules/alsa/module-alsa-card.c
@@ -38,6 +38,7 @@
 
 #include "alsa-util.h"
 #include "alsa-ucm.h"
+#include "alsa-extcon.h"
 #include "alsa-sink.h"
 #include "alsa-source.h"
 #include "module-alsa-card-symdef.h"
@@ -114,6 +115,7 @@ struct userdata {
     snd_hctl_t *hctl_handle;
     pa_hashmap *jacks;
     pa_alsa_fdlist *mixer_fdl;
+    pa_alsa_extcon *extcon;
 
     pa_card *card;
 
@@ -750,6 +752,7 @@ int pa__init(pa_module *m) {
     u->card->set_profile = card_set_profile;
 
     init_jacks(u);
+    u->extcon = pa_alsa_extcon_new(m->core, u->card);
     init_profile(u);
     init_eld_ctls(u);
 
@@ -815,6 +818,8 @@ void pa__done(pa_module*m) {
     if (u->source_output_unlink_hook_slot)
         pa_hook_slot_free(u->source_output_unlink_hook_slot);
 
+    if (u->extcon)
+        pa_alsa_extcon_free(u->extcon);
     if (u->mixer_fdl)
         pa_alsa_fdlist_free(u->mixer_fdl);
     if (u->mixer_handle)
-- 
1.7.9.5



More information about the pulseaudio-discuss mailing list