summaryrefslogtreecommitdiff
path: root/sys/dev/audio.c
diff options
context:
space:
mode:
authorjmcneill <jmcneill@NetBSD.org>2007-12-09 20:27:42 +0000
committerjmcneill <jmcneill@NetBSD.org>2007-12-09 20:27:42 +0000
commit4c1d81b2b5fa658e9fd5e7151eb40ba71ba676da (patch)
tree3f8b0d6c63745bde8809b8c6f9e4f6b1514f335e /sys/dev/audio.c
parent3947df41d16da8b387239b9b87789bfbe950a3b2 (diff)
Merge jmcneill-pm branch.
Diffstat (limited to 'sys/dev/audio.c')
-rw-r--r--sys/dev/audio.c306
1 files changed, 270 insertions, 36 deletions
diff --git a/sys/dev/audio.c b/sys/dev/audio.c
index f086955e929..dee292c5ab2 100644
--- a/sys/dev/audio.c
+++ b/sys/dev/audio.c
@@ -1,4 +1,4 @@
-/* $NetBSD: audio.c,v 1.226 2007/12/05 17:19:47 pooka Exp $ */
+/* $NetBSD: audio.c,v 1.227 2007/12/09 20:27:51 jmcneill Exp $ */
/*
* Copyright (c) 1991-1993 Regents of the University of California.
@@ -61,7 +61,7 @@
*/
#include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: audio.c,v 1.226 2007/12/05 17:19:47 pooka Exp $");
+__KERNEL_RCSID(0, "$NetBSD: audio.c,v 1.227 2007/12/09 20:27:51 jmcneill Exp $");
#include "audio.h"
#if NAUDIO > 0
@@ -102,6 +102,7 @@ int audiodebug = AUDIO_DEBUG;
#define SPECIFIED(x) (x != ~0)
#define SPECIFIED_CH(x) (x != (u_char)~0)
+int audio_idle_timeout = 30;
int audio_blk_ms = AUDIO_BLK_MS;
int audiosetinfo(struct audio_softc *, struct audio_info *);
@@ -173,7 +174,17 @@ void audioattach(struct device *, struct device *, void *);
int audiodetach(struct device *, int);
int audioactivate(struct device *, enum devact);
-void audio_powerhook(int, void *);
+static void audio_idle(void *);
+static void audio_activity(device_t, devactive_t);
+
+static bool audio_suspend(device_t dv);
+static bool audio_resume(device_t dv);
+static void audio_volume_down(device_t);
+static void audio_volume_up(device_t);
+static void audio_volume_toggle(device_t);
+
+static void audio_mixer_capture(struct audio_softc *);
+static void audio_mixer_restore(struct audio_softc *);
static void audio_softintr_rd(void *);
static void audio_softintr_wr(void *);
@@ -333,6 +344,8 @@ audioattach(struct device *parent, struct device *self, void *aux)
return;
}
+ sc->sc_lastgain = 128;
+
if ((error = audio_set_defaults(sc, 0))) {
aprint_error("audioattach: audio_set_defaults() failed\n");
sc->hw_if = NULL;
@@ -419,6 +432,9 @@ audioattach(struct device *parent, struct device *self, void *aux)
au_setup_ports(sc, &sc->sc_inports, &mi,
itable);
}
+ if (strcmp(mi.label.name, AudioNdac) == 0 &&
+ sc->sc_outports.master == -1)
+ sc->sc_outports.master = mi.index;
} else if (mi.mixer_class == mclass) {
if (strcmp(mi.label.name, AudioNmonitor) == 0)
sc->sc_monitor_port = mi.index;
@@ -467,11 +483,25 @@ audioattach(struct device *parent, struct device *self, void *aux)
sc->sc_inports.allports, sc->sc_inports.master,
sc->sc_outports.allports, sc->sc_outports.master));
- sc->sc_powerhook = powerhook_establish(sc->dev.dv_xname,
- audio_powerhook, sc);
- if (sc->sc_powerhook == NULL)
- aprint_error("%s: can't establish powerhook\n",
- sc->dev.dv_xname);
+ callout_init(&sc->sc_idle_counter, 0);
+ callout_setfunc(&sc->sc_idle_counter, audio_idle, self);
+
+ if (!pmf_device_register(self, audio_suspend, audio_resume))
+ aprint_error_dev(self, "couldn't establish power handler\n");
+ if (!device_active_register(self, audio_activity))
+ aprint_error_dev(self, "couldn't register activity handler\n");
+
+ if (!pmf_event_register(self, PMFE_AUDIO_VOLUME_DOWN,
+ audio_volume_down, true))
+ aprint_error_dev(self, "couldn't add volume down handler\n");
+ if (!pmf_event_register(self, PMFE_AUDIO_VOLUME_UP,
+ audio_volume_up, true))
+ aprint_error_dev(self, "couldn't add volume up handler\n");
+ if (!pmf_event_register(self, PMFE_AUDIO_VOLUME_TOGGLE,
+ audio_volume_toggle, true))
+ aprint_error_dev(self, "couldn't add volume toggle handler\n");
+
+ callout_schedule(&sc->sc_idle_counter, audio_idle_timeout * hz);
}
int
@@ -503,6 +533,17 @@ audiodetach(struct device *self, int flags)
sc->sc_dying = true;
+ pmf_event_deregister(self, PMFE_AUDIO_VOLUME_DOWN,
+ audio_volume_down, true);
+ pmf_event_deregister(self, PMFE_AUDIO_VOLUME_UP,
+ audio_volume_up, true);
+ pmf_event_deregister(self, PMFE_AUDIO_VOLUME_TOGGLE,
+ audio_volume_toggle, true);
+
+ callout_stop(&sc->sc_idle_counter);
+
+ pmf_device_deregister(self);
+
wakeup(&sc->sc_wchan);
wakeup(&sc->sc_rchan);
s = splaudio();
@@ -529,10 +570,6 @@ audiodetach(struct device *self, int flags)
vdevgone(maj, mn | AUDIOCTL_DEVICE, mn | AUDIOCTL_DEVICE, VCHR);
vdevgone(maj, mn | MIXER_DEVICE, mn | MIXER_DEVICE, VCHR);
- if (sc->sc_powerhook != NULL) {
- powerhook_disestablish(sc->sc_powerhook);
- sc->sc_powerhook = NULL;
- }
if (sc->sc_sih_rd) {
softint_disestablish(sc->sc_sih_rd);
sc->sc_sih_rd = NULL;
@@ -542,6 +579,8 @@ audiodetach(struct device *self, int flags)
sc->sc_sih_wr = NULL;
}
+ callout_destroy(&sc->sc_idle_counter);
+
return 0;
}
@@ -949,8 +988,8 @@ audioopen(dev_t dev, int flags, int ifmt, struct lwp *l)
if (sc->sc_dying)
return EIO;
- if (sc->hw_if->powerstate && sc->sc_opencnt <= 0)
- sc->hw_if->powerstate(sc->hw_hdl, AUDIOPOWER_ON);
+ device_active(&sc->dev, DVA_SYSTEM);
+
sc->sc_opencnt++;
sc->sc_refcnt++;
@@ -983,6 +1022,9 @@ audioclose(dev_t dev, int flags, int ifmt, struct lwp *l)
unit = AUDIOUNIT(dev);
sc = audio_cd.cd_devs[unit];
+
+ device_active(&sc->dev, DVA_SYSTEM);
+
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
case AUDIO_DEVICE:
@@ -1000,8 +1042,6 @@ audioclose(dev_t dev, int flags, int ifmt, struct lwp *l)
}
sc->sc_opencnt--;
- if (sc->hw_if->powerstate && sc->sc_opencnt <= 0)
- sc->hw_if->powerstate(sc->hw_hdl, AUDIOPOWER_OFF);
return error;
}
@@ -1019,6 +1059,8 @@ audioread(dev_t dev, struct uio *uio, int ioflag)
if (sc->sc_dying)
return EIO;
+ device_active(&sc->dev, DVA_SYSTEM);
+
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
@@ -1051,6 +1093,8 @@ audiowrite(dev_t dev, struct uio *uio, int ioflag)
if (sc->sc_dying)
return EIO;
+ device_active(&sc->dev, DVA_SYSTEM);
+
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
@@ -1085,6 +1129,7 @@ audioioctl(dev_t dev, u_long cmd, void *addr, int flag, struct lwp *l)
case SOUND_DEVICE:
case AUDIO_DEVICE:
case AUDIOCTL_DEVICE:
+ device_active(&sc->dev, DVA_SYSTEM);
error = audio_ioctl(sc, cmd, addr, flag, l);
break;
case MIXER_DEVICE:
@@ -1109,6 +1154,8 @@ audiopoll(dev_t dev, int events, struct lwp *l)
if (sc->sc_dying)
return POLLHUP;
+ device_active(&sc->dev, DVA_SYSTEM);
+
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
@@ -1138,6 +1185,8 @@ audiokqfilter(dev_t dev, struct knote *kn)
if (sc->sc_dying)
return 1;
+ device_active(&sc->dev, DVA_SYSTEM);
+
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
@@ -1166,6 +1215,8 @@ audiommap(dev_t dev, off_t off, int prot)
if (sc->sc_dying)
return -1;
+ device_active(&sc->dev, DVA_SYSTEM); /* XXXJDM */
+
sc->sc_refcnt++;
switch (AUDIODEV(dev)) {
case SOUND_DEVICE:
@@ -3713,12 +3764,18 @@ mixer_ioctl(struct audio_softc *sc, u_long cmd, void *addr, int flag,
struct lwp *l)
{
const struct audio_hw_if *hw;
+ mixer_ctrl_t *mc;
int error;
DPRINTF(("mixer_ioctl(%lu,'%c',%lu)\n",
IOCPARM_LEN(cmd), (char)IOCGROUP(cmd), cmd&0xff));
hw = sc->hw_if;
error = EINVAL;
+
+ /* we can return cached values if we are sleeping */
+ if (cmd != AUDIO_MIXER_READ)
+ device_active(&sc->dev, DVA_SYSTEM);
+
switch (cmd) {
case FIOASYNC:
mixer_remove(sc, l); /* remove old entry */
@@ -3746,7 +3803,19 @@ mixer_ioctl(struct audio_softc *sc, u_long cmd, void *addr, int flag,
case AUDIO_MIXER_READ:
DPRINTF(("AUDIO_MIXER_READ\n"));
- error = hw->get_port(sc->hw_hdl, (mixer_ctrl_t *)addr);
+ mc = (mixer_ctrl_t *)addr;
+
+ if (device_is_active(sc->sc_dev) ||
+ sc->sc_mixer_state == NULL)
+ error = hw->get_port(sc->hw_hdl, mc);
+ else if (mc->dev >= sc->sc_nmixer_states)
+ error = ENXIO;
+ else {
+ int dev = mc->dev;
+ memcpy(mc, &sc->sc_mixer_state[dev],
+ sizeof(mixer_ctrl_t));
+ error = 0;
+ }
break;
case AUDIO_MIXER_WRITE:
@@ -3814,30 +3883,195 @@ audioprint(void *aux, const char *pnp)
#endif /* NAUDIO > 0 || (NMIDI > 0 || NMIDIBUS > 0) */
#if NAUDIO > 0
-void
-audio_powerhook(int why, void *aux)
+static void
+audio_mixer_capture(struct audio_softc *sc)
{
- struct audio_softc *sc;
- const struct audio_hw_if *hwp;
+ mixer_devinfo_t mi;
+ mixer_ctrl_t *mc;
- sc = (struct audio_softc *)aux;
- hwp = sc->hw_if;
+ for (mi.index = 0; ; mi.index++)
+ if (sc->hw_if->query_devinfo(sc->hw_hdl, &mi) != 0)
+ break;
- switch (why) {
- case PWR_SOFTSUSPEND:
- if (sc->sc_pbus == true)
- hwp->halt_output(sc->hw_hdl);
- if (sc->sc_rbus == true)
- hwp->halt_input(sc->hw_hdl);
- break;
- case PWR_SOFTRESUME:
- if (sc->sc_pbus == true)
- audiostartp(sc);
- if (sc->sc_rbus == true)
- audiostartr(sc);
- break;
+#ifdef DIAGNOSTIC
+ if (sc->sc_mixer_state != NULL && sc->sc_nmixer_states != mi.index) {
+ free(sc->sc_mixer_state, M_DEVBUF);
+ sc->sc_mixer_state = NULL;
+ }
+#endif
+
+ sc->sc_nmixer_states = mi.index;
+ if (sc->sc_mixer_state == NULL)
+ sc->sc_mixer_state = malloc(
+ sizeof(mixer_ctrl_t) * sc->sc_nmixer_states,
+ M_DEVBUF, M_NOWAIT);
+ if (sc->sc_mixer_state == NULL) {
+ aprint_error("%s: couldn't allocate memory for mixer state\n",
+ device_xname(&sc->dev));
+ return;
+ }
+
+ for (mi.index = 0; ; mi.index++) {
+ if (sc->hw_if->query_devinfo(sc->hw_hdl, &mi) != 0)
+ break;
+ if (mi.type == AUDIO_MIXER_CLASS)
+ continue;
+ mc = &sc->sc_mixer_state[mi.index];
+ mc->dev = mi.index;
+ mc->type = mi.type;
+ (void)sc->hw_if->get_port(sc->hw_hdl, mc);
}
return;
}
+
+static void
+audio_mixer_restore(struct audio_softc *sc)
+{
+ mixer_devinfo_t mi;
+ mixer_ctrl_t *mc;
+
+ if (sc->sc_mixer_state == NULL)
+ return;
+
+ for (mi.index = 0; ; mi.index++) {
+ if (sc->hw_if->query_devinfo(sc->hw_hdl, &mi) != 0)
+ break;
+ if (mi.type == AUDIO_MIXER_CLASS)
+ continue;
+ mc = &sc->sc_mixer_state[mi.index];
+ (void)sc->hw_if->set_port(sc->hw_hdl, mc);
+ }
+ if (sc->hw_if->commit_settings)
+ sc->hw_if->commit_settings(sc->hw_hdl);
+
+ return;
+}
+
+static void
+audio_idle(void *arg)
+{
+ device_t dv = arg;
+ struct audio_softc *sc = device_private(dv);
+
+#ifdef PNP_DEBUG
+ extern int pnp_debug_idle;
+ if (pnp_debug_idle)
+ printf("%s: idle handler called\n", device_xname(dv));
+#endif
+
+ /* XXX joerg Make pmf_device_suspend handle children? */
+ if (!pmf_device_suspend(dv))
+ return;
+
+ if (!pmf_device_suspend(sc->sc_dev))
+ pmf_device_resume(dv);
+}
+
+static void
+audio_activity(device_t dv, devactive_t type)
+{
+ struct audio_softc *sc = device_private(dv);
+
+ if (type != DVA_SYSTEM)
+ return;
+
+ callout_schedule(&sc->sc_idle_counter, audio_idle_timeout * hz);
+
+ if (!device_is_active(dv)) {
+ /* XXX joerg How to deal with a failing resume... */
+ pmf_device_resume(sc->sc_dev);
+ pmf_device_resume(dv);
+ }
+}
+
+static bool
+audio_suspend(device_t dv)
+{
+ struct audio_softc *sc = device_private(dv);
+ const struct audio_hw_if *hwp = sc->hw_if;
+ int (s);
+
+ s = splaudio();
+ audio_mixer_capture(sc);
+ if (sc->sc_pbus == true)
+ hwp->halt_output(sc->hw_hdl);
+ if (sc->sc_rbus == true)
+ hwp->halt_input(sc->hw_hdl);
+ callout_stop(&sc->sc_idle_counter);
+ splx(s);
+
+ return true;
+}
+
+static bool
+audio_resume(device_t dv)
+{
+ struct audio_softc *sc = device_private(dv);
+ int s;
+
+ s = splaudio();
+ audio_mixer_restore(sc);
+ if (sc->sc_pbus == true)
+ audiostartp(sc);
+ if (sc->sc_rbus == true)
+ audiostartr(sc);
+ splx(s);
+
+ return true;
+}
+
+static void
+audio_volume_down(device_t dv)
+{
+ struct audio_softc *sc = device_private(dv);
+ u_int gain, newgain;
+ u_char balance;
+ int s;
+
+ s = splaudio();
+ au_get_gain(sc, &sc->sc_outports, &gain, &balance);
+ newgain = gain - 32;
+ if (newgain > 255)
+ newgain = 0;
+ au_set_gain(sc, &sc->sc_outports, newgain, balance);
+ splx(s);
+}
+
+static void
+audio_volume_up(device_t dv)
+{
+ struct audio_softc *sc = device_private(dv);
+ u_int gain, newgain;
+ u_char balance;
+ int s;
+
+ s = splaudio();
+ au_get_gain(sc, &sc->sc_outports, &gain, &balance);
+ newgain = gain + 32;
+ if (newgain > 255)
+ newgain = 255;
+ au_set_gain(sc, &sc->sc_outports, newgain, balance);
+ splx(s);
+}
+
+static void
+audio_volume_toggle(device_t dv)
+{
+ struct audio_softc *sc = device_private(dv);
+ u_int gain, newgain;
+ u_char balance;
+ int s;
+
+ s = splaudio();
+ au_get_gain(sc, &sc->sc_outports, &gain, &balance);
+ if (gain != 0) {
+ sc->sc_lastgain = gain;
+ newgain = sc->sc_lastgain;
+ } else
+ newgain = 0;
+ au_set_gain(sc, &sc->sc_outports, newgain, balance);
+ splx(s);
+}
+
#endif /* NAUDIO > 0 */