[pulseaudio-discuss] [PATCH 1/4] alsa: Add extcon (Android switch) jack detection
Tanu Kaskinen
tanu.kaskinen at linux.intel.com
Tue Jun 24 05:27:04 PDT 2014
On Wed, 2013-09-18 at 16:01 +0200, David Henningsson wrote:
> 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 \
Apparently the code depends on UCM and udev. I think these files should
be added to SOURCES only if both UCM and udev are available. The two
calls in alsa-card.c need to be then protected with #ifdefs too.
Another alternative is to do the ifdeffery in alsa-extcon.h and
alsa-extcon.c only, similar to how alsa-ucm.h and .c do it. I'd be fine
with that too.
> 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;
Some commenting would be nice: what do the first and second bits mean
that you check here?
> +}
> +
> +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;
I suggest storing the decoded pa_available_t value here instead of the
raw integer.
> +};
> +
> +static void android_switch_free(struct android_switch *as) {
> + if (!as)
> + return;
Style: don't accept NULL in free().
> + 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;
> +};
Minor bike-shedding, feel free to ignore:
The type "struct udev_data" is only used as a member of pa_alsa_extcon,
so having a stand-alone type is unnecessary. I think it one of these
would be neater:
struct pa_alsa_extcon {
pa_card *card;
struct android_switch *h2w;
struct {
struct udev *udev;
struct udev_monitor *monitor;
pa_io_event *event;
} udev;
};
or
struct pa_alsa_extcon {
pa_card *card;
struct android_switch *h2w;
struct udev *udev;
struct udev_monitor *udev_monitor;
pa_io_event *udev_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"))
Use pa_streq().
> + 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_alsa_ucm_port_contains() assumes that the port name follows the
format that alsa-ucm.c uses when generating port names. I think it would
be good to check if the card is actually using UCM before calling
pa_alsa_ucm_port_contains(). This is not a big issue in practice (the
only "problem" is accidental match in a non-UCM port name), but still
I'd prefer not feeding "garbage" to pa_alsa_ucm_port_contains().
> + 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"))
Use pa_streq().
> + 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.");
Copy-paste error in log message.
> + 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) {
Use int for success/failure reporting.
> +
> + 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 */
Do you plan to do the research?
--
Tanu
More information about the pulseaudio-discuss
mailing list