blob: 4517d0d9460e2d36d6f39e704e4b0e4df4a25a32 [file] [log] [blame]
// Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <alsa/asoundlib.h>
#include <dirent.h>
#include <linux/input.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "audio_utils.h"
#ifdef USE_ALSA_CONTROL
static int aud_verbose = 0;
static struct aud_jack_event hp_jack;
static char card[64] = "default";
static int aud_switch(enum aud_cmd cmd, const char *name, int *value)
{
int err = 0;
snd_mixer_t *handle;
snd_mixer_selem_id_t *sid;
snd_mixer_elem_t *elem;
snd_mixer_selem_id_alloca(&sid);
if ((err = snd_mixer_open(&handle, 0)) < 0) {
aud_errno("mixer %s open", card);
return err;
}
if ((err = snd_mixer_attach(handle, card)) < 0) {
aud_errno("mixer attach %s", card);
snd_mixer_close(handle);
return err;
}
if ((err = snd_mixer_selem_register(handle, NULL, NULL)) < 0) {
aud_errno("mixer register");
snd_mixer_close(handle);
return err;
}
if ((err = snd_mixer_load(handle)) < 0) {
aud_errno("mixer %s load", card);
snd_mixer_close(handle);
return err;
}
for (elem = snd_mixer_first_elem(handle); elem;
elem = snd_mixer_elem_next(elem)) {
snd_mixer_selem_get_id(elem, sid);
if (!(strcmp(snd_mixer_selem_id_get_name(sid), name))) {
if (aud_verbose)
printf("Audio: found %s audio switch\n", name);
if (snd_mixer_selem_has_playback_switch(elem)) {
switch (cmd) {
case get:
err = snd_mixer_selem_get_playback_switch(elem, SND_MIXER_SCHN_MONO,
value);
if (err < 0)
aud_errno("getting %s", name);
break;
case set:
err = snd_mixer_selem_set_playback_switch_all(elem, *value);
if (err < 0)
aud_errno("setting %s -> %d", name, *value);
break;
default:
aud_error("unknown switch cmd %d for %s\n", cmd, name);
err = -1;
break;
}
}
}
}
snd_mixer_close(handle);
return err;
}
static int set_speaker_switch(int value)
{
int *pval = &value;
return aud_switch(set, "Speaker", pval);
}
static int set_spdif_switch(int value)
{
int *pval = &value;
return aud_switch(set, "IEC958", pval);
}
static int open_jack_event(int etype)
{
DIR *ev_dir;
struct dirent *ev_entry;
int fd = -1;
assert(etype & SW_HEADPHONE_INSERT);
if((ev_dir = opendir(INPUT_DIR)) == NULL) {
aud_errno("opening dir %s", INPUT_DIR);
return -1;
}
if (chdir(INPUT_DIR)) {
aud_errno("chdir to %s", INPUT_DIR);
return -1;
}
while ((ev_entry = readdir(ev_dir))) {
if ((ev_entry->d_type == DT_CHR) &&
!(strncmp(ev_entry->d_name, "event", 5))) {
if (access(ev_entry->d_name, R_OK) < 0) {
aud_errno("read access %s", ev_entry->d_name);
return -1;
}
if ((fd = open(ev_entry->d_name, O_RDONLY)) < 0) {
aud_errno("opening event %s", ev_entry->d_name);
return -1;
}
unsigned long events[NBITS(EV_MAX)];
memset(events, 0, sizeof(events));
if (ioctl(fd, EVIOCGBIT(0, EV_MAX), events) < 0) {
aud_errno("ioctl EVIOCGBIT for events");
return -1;
}
if (IS_BIT_SET(EV_SW, events)) {
unsigned long sw_events[NBITS(SW_MAX)];
memset(sw_events, 0, sizeof(sw_events));
if (ioctl(fd, EVIOCGBIT(EV_SW, SW_MAX), sw_events) < 0) {
aud_errno("ioctl EVIOCGBIT for SW events");
return -1;
}
if (IS_BIT_SET(etype, sw_events)) {
break;
}
}
}
}
return fd;
}
static int open_jack_hp_event(struct aud_jack_event *jack) {
jack->etype = SW_HEADPHONE_INSERT;
jack->fd = open_jack_event(SW_HEADPHONE_INSERT);
jack->status = unknown;
return jack->fd;
}
static void get_jack_status(struct aud_jack_event *jack) {
unsigned long events[NBITS(SW_MAX)];
assert(jack->fd > 0);
memset(events, 0, sizeof(events));
ioctl(jack->fd, EVIOCGSW(sizeof(events)), events);
jack->status = IS_BIT_SET(hp_jack.etype, events);
}
static void close_jack_event(struct aud_jack_event *jack)
{
close(jack->fd);
}
#define FORCE_MUTE_FILE "/var/run/chrontel/forced_mute"
static int get_force_mute()
{
return (access(FORCE_MUTE_FILE, R_OK) == 0);
}
static void enable_hdmi_audio()
{
int fd;
do {
fd = creat(FORCE_MUTE_FILE, S_IRUSR | S_IWUSR);
} while ((fd == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)));
if (fd == -1) {
aud_errno("creating %s", FORCE_MUTE_FILE);
} else {
close(fd);
}
set_speaker_switch(0);
if (aud_verbose) printf("Audio: muted Speaker\n");
set_spdif_switch(1);
if (aud_verbose) printf("Audio: enabled SPDIF\n");
}
static void disable_hdmi_audio()
{
int err;
do {
err = remove(FORCE_MUTE_FILE);
} while ((err == -1 ) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)));
if (err == -1)
aud_errno("removing %s", FORCE_MUTE_FILE);
set_speaker_switch(1);
if (aud_verbose) printf("Audio: unmuted Speaker\n");
set_spdif_switch(0);
if (aud_verbose) printf("Audio: disabled SPDIF\n");
}
#endif /* USE_ALSA_CONTROL */
int audio_init(int verbose)
{
int err = 0;
#ifdef USE_ALSA_CONTROL
aud_verbose = verbose;
if ((err = open_jack_hp_event(&hp_jack)) < 0) {
aud_error("opening hp jack event\n");
} else {
get_jack_status(&hp_jack);
}
#endif
return err;
}
void audio_to_hdmi(int enable)
{
#ifdef USE_ALSA_CONTROL
if (aud_verbose) printf("Audio: Set HDMI audio output to %s\n",
enable ? "Enable" : "Disable");
if (enable) {
get_jack_status(&hp_jack);
if (aud_verbose) printf("Audio: headphone is %s\n",
hp_jack.status ? "plugged-in" : "unplugged");
if (hp_jack.status == unplugged)
enable_hdmi_audio();
else if (get_force_mute())
disable_hdmi_audio();
} else {
if (get_force_mute())
disable_hdmi_audio();
}
#endif
}
void audio_check_jack(int enable)
{
#ifdef USE_ALSA_CONTROL
if (enable == 0)
return;
enum jack_status cur_status = hp_jack.status;
get_jack_status(&hp_jack);
if (hp_jack.status != cur_status)
audio_to_hdmi(enable);
#endif
}