Index: sys/arch/i386/conf/GENERIC
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/arch/i386/conf/GENERIC,v
retrieving revision 1.905
diff -u -r1.905 GENERIC
--- sys/arch/i386/conf/GENERIC  21 Aug 2008 18:06:18 -0000      1.905
+++ sys/arch/i386/conf/GENERIC  19 Sep 2008 09:45:49 -0000
@@ -1312,7 +1312,8 @@

# The spkr driver provides a simple tone interface to the built in speaker.
#spkr0 at pcppi?               # PC speaker
-
+# (EXPERIMENTAL) PC speaker driven by PCM. Exclusive to spkr0 (above)
+pcmaudio*  at pcppi?

# FM-Radio devices
# ISA radio devices
Index: sys/arch/x86/isa/clock.c
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/arch/x86/isa/clock.c,v
retrieving revision 1.30
diff -u -r1.30 clock.c
--- sys/arch/x86/isa/clock.c    11 May 2008 22:18:08 -0000      1.30
+++ sys/arch/x86/isa/clock.c    19 Sep 2008 08:45:07 -0000
@@ -175,6 +175,16 @@
static pcppi_tag_t ppicookie;
#endif /* PCPPI */

+#include "pcmaudio.h"
+#if NPCMAUDIO > 0
+#include <dev/isa/pcmaudiovar.h>
+struct pcmaudio_softc *pcmaudio_softc;
+
+static u_long tvaldiv = 0;
+static u_long hardskip = 0;
+#endif
+
+
#ifdef CLOCKDEBUG
int clock_debug = 0;
#define DPRINTF(arg) if (clock_debug) printf arg
@@ -182,6 +192,8 @@
#define DPRINTF(arg)
#endif

+extern void (*initclock_func)(void); /* XXX put in header file */
+
/* Used by lapic.c */
unsigned int   gettick(void);
void           sysbeep(int, int);
@@ -336,7 +348,30 @@
        * set to.  Also, correctly round
        * this by carrying an extra bit through the division.
        */
+
+#if NPCMAUDIO > 0
+       struct pcmaudio_softc *sc = pcmaudio_softc;
+       int samplingrate;
+
+       if (sc == NULL) { /* Before pcmaudio(4) is attached. */
+               samplingrate = hz;
+       }
+       else { /* After pcmaudio(4) has attached. */
+               samplingrate = sc->sc_samplingrate;
+               /* Save a reference to i8254_timecounter */
+               sc->sc_timecounter = &i8254_timecounter;
+       }
+
+       /* Adjust the timecounter spec
+        * i8254_timecounter.quality = samplingrate
+        */
+       tval = (freq * 2) / (u_long) samplingrate;
+       tvaldiv = samplingrate / (u_long) hz;
+
+
+#else
       tval = (freq * 2) / (u_long) hz;
+#endif
       tval = (tval / 2) + (tval & 0x1);

       /* initialize 8254 clock */
@@ -400,9 +435,61 @@
static int
clockintr(void *arg, struct intrframe *frame)
{
+
       tickle_tc();

-       hardclock((struct clockframe *)frame);
+#if NPCMAUDIO > 0
+
+       struct pcmaudio_softc *sc = pcmaudio_softc;
+       u_long tval;
+
+       if ((sc != NULL) && (sc->playing == true)) {
+               if (sc->blknow < sc->blkend) {
+
+                       /* Processing: signed int -> unsigned long, scale wrt rtclock_tval */
+                       tval = (((u_long)((1 << 15) + (signed long)*(sc->blknow++)) * rtclock_tval) >> 16) | 0x1;
+
+                       /* Adjust volume */
+                       tval = tval * sc->sc_spkrvolume / AUDIO_MAX_GAIN;
+
+                       /* Init counter 2, tied to the PC Speaker. */
+                       outb(IO_TIMER1 + TIMER_MODE, TIMER_SEL2 | TIMER_16BIT | TIMER_INTTC);
+                       outb(IO_TIMER1 + TIMER_CNTR2, tval % 256);
+                       outb(IO_TIMER1 + TIMER_CNTR2, tval / 256);
+               }
+               else {
+                       /* reset the blk pointers */
+                       sc->blknow = sc->blkstart;
+               }
+
+               if (!((size_t) sc->blknow % sc->blksize)) {
+                       /* schedule the audio(4) callback */
+
+                       KASSERT(sc->sc_softintcookie != NULL);
+                       softint_schedule(sc->sc_softintcookie);
+               }
+       }
+
+       if (hardskip < tvaldiv){
+               hardskip++;
+               goto eoi;
+       }
+
+       hardskip = 0;
+
+#endif
+
+       /*
+        * We only handle the system clock if we're really in charge
+        * of it.
+        */
+       if (initclock_func == i8254_initclocks) {
+               hardclock((struct clockframe *)frame);
+       }
+
+#if NPCMAUDIO > 0
+eoi:
+#endif

#if NMCA > 0
       if (MCA_system) {
@@ -410,6 +497,7 @@
               outb(0x61, inb(0x61) | 0x80);
       }
#endif
+
       return -1;
}

@@ -575,13 +663,14 @@
void
i8254_initclocks(void)
{
-
+
       /*
        * XXX If you're doing strange things with multiple clocks, you might
        * want to keep track of clock handlers.
        */
-       (void)isa_intr_establish(NULL, 0, IST_PULSE, IPL_CLOCK,
-           (int (*)(void *))clockintr, 0);
+       i8254_timecounter.tc_priv = isa_intr_establish(NULL, 0, IST_PULSE, IPL_CLOCK,
+                                                      (int (*)(void *))clockintr, 0);
+
}

static void
Index: sys/dev/audio.c
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/dev/audio.c,v
retrieving revision 1.243
diff -u -r1.243 audio.c
--- sys/dev/audio.c     10 Jun 2008 22:53:08 -0000      1.243
+++ sys/dev/audio.c     17 Sep 2008 07:38:09 -0000
@@ -3583,6 +3583,10 @@
               s = splaudio();
               init_error = audio_initbufs(sc);
               if (init_error) goto err;
+               if (sc->sc_pustream == NULL ||
+                   sc->sc_rustream == NULL) {
+                       goto err;
+               }
               if (sc->sc_pr.blksize != oldpblksize ||
                   sc->sc_rr.blksize != oldrblksize ||
                   sc->sc_pustream != oldpus ||
Index: sys/dev/ic/attimer.c
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/dev/ic/attimer.c,v
retrieving revision 1.9
diff -u -r1.9 attimer.c
--- sys/dev/ic/attimer.c        12 Jun 2008 22:30:30 -0000      1.9
+++ sys/dev/ic/attimer.c        14 Jul 2008 05:22:15 -0000
@@ -112,3 +112,19 @@
           TIMER_DIV(pitch) / 256);
       splx(s);
}
+
+void
+attimer_set_pulse(device_t dev, int lowcnt)
+{
+       struct attimer_softc *sc = device_private(dev);
+       int s;
+
+       s = splhigh();
+       bus_space_write_1(sc->sc_iot, sc->sc_ioh, TIMER_MODE,
+           TIMER_SEL2 | TIMER_16BIT | TIMER_INTTC);
+       bus_space_write_1(sc->sc_iot, sc->sc_ioh, TIMER_CNTR2,
+           TIMER_DIV(lowcnt) % 256);
+       bus_space_write_1(sc->sc_iot, sc->sc_ioh, TIMER_CNTR2,
+           TIMER_DIV(lowcnt) / 256);
+       splx(s);
+}
Index: sys/dev/ic/attimervar.h
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/dev/ic/attimervar.h,v
retrieving revision 1.6
diff -u -r1.6 attimervar.h
--- sys/dev/ic/attimervar.h     29 Apr 2008 06:53:02 -0000      1.6
+++ sys/dev/ic/attimervar.h     18 Jul 2008 10:31:30 -0000
@@ -40,3 +40,4 @@
int            attimer_detach(device_t, int);
device_t       attimer_attach_speaker(void);
void           attimer_set_pitch(device_t, int);
+void           attimer_set_pulse(device_t, int);
Index: sys/dev/isa/files.isa
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/dev/isa/files.isa,v
retrieving revision 1.157
diff -u -r1.157 files.isa
--- sys/dev/isa/files.isa       3 Apr 2008 22:46:22 -0000       1.157
+++ sys/dev/isa/files.isa       19 Sep 2008 08:03:30 -0000
@@ -443,6 +443,9 @@
file   dev/isa/spkr.c                  spkr                    needs-flag
attach midi at pcppi with midi_pcppi: midisyn
file   dev/isa/midi_pcppi.c            midi_pcppi
+device pcmaudio: audiobus, auconv, mulaw, aurateconv
+attach         pcmaudio at pcppi
+file   dev/isa/pcmaudio.c              pcmaudio                needs-flag

# AT Timer (TIMER 1)
attach attimer at isa with attimer_isa
Index: sys/dev/isa/pcmaudio.c
===================================================================
RCS file: sys/dev/isa/pcmaudio.c
diff -N sys/dev/isa/pcmaudio.c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ sys/dev/isa/pcmaudio.c      19 Sep 2008 14:03:36 -0000
@@ -0,0 +1,826 @@
+/*     $NetBSD$         */
+
+/*-
+ * Copyright (c) 2008 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Written by Cherry G. Mathew <[email protected]> with bits and pieces
+ * from auich(4) and ym(4)
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/errno.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/param.h>
+#include <sys/cpu.h>
+#include <sys/fcntl.h>
+#include <sys/kernel.h>
+#include <sys/sysctl.h>
+
+#include <sys/audioio.h>
+#include <dev/audio_if.h>
+#include <dev/mulaw.h>
+#include <dev/auconv.h>
+
+#include <dev/ic/attimervar.h>
+#include <dev/ic/i8253reg.h>
+#include <dev/isa/isavar.h>
+
+#include "pcmaudio.h"
+
+#include <dev/isa/pcmaudiovar.h>
+#include <dev/isa/pcppivar.h>
+#include <dev/isa/pcppireg.h>
+
+#include <uvm/uvm_extern.h>
+#include <uvm/uvm_map.h>
+
+
+extern void (*initclock_func)(void); /* XXX put in header file */
+
+static int pcmaudio_open(void *, int);
+static void pcmaudio_close(void *);
+static int pcmaudio_query_encoding(void *, audio_encoding_t *);
+static int pcmaudio_set_params(void *, int, int, audio_params_t *,
+                              audio_params_t *, stream_filter_list_t *,
+                              stream_filter_list_t *);
+static int pcmaudio_round_blocksize(void *, int, int, const audio_params_t *);
+static int pcmaudio_halt_output(void *);
+static int pcmaudio_halt_input(void *);
+
+static int pcmaudio_speaker_ctl(void *, int);
+static int pcmaudio_getdev(void *, struct audio_device *);
+
+/* Mixer functions. Stubs for now. */
+static int pcmaudio_set_port(void *, mixer_ctrl_t *);
+static int pcmaudio_get_port(void *, mixer_ctrl_t *);
+
+static int pcmaudio_query_devinfo(void *, mixer_devinfo_t *);
+
+
+static size_t pcmaudio_round_buffersize(void *, int, size_t);
+static paddr_t pcmaudio_mappage(void *, void *, off_t, int);
+static int pcmaudio_get_props(void *);
+static         int pcmaudio_trigger_output(void *, void *, void *, int, void (*)(void *),
+                                   void *, const audio_params_t *);
+static         int pcmaudio_trigger_input(void *, void *, void *, int, void (*)(void *),
+                                  void *, const audio_params_t *);
+static int pcmaudio_sysctl_verify(SYSCTLFN_ARGS);
+
+extern struct pcmaudio_softc *pcmaudio_softc;
+
+/* In the hypothetical case that there is more than one pcmaudio(4)
+ * instance attached during autoconf, the sampling rate is uniform
+ * across these instances. This situation is untested.
+ */
+static int pcmaudio_sampling_rate = 22000;
+
+static const struct audio_format pcmaudio_formats[PCMAUDIO_NFORMATS] = {
+       {
+               .driver_data            = NULL,
+               .mode                   = AUMODE_PLAY,
+               .encoding               = AUDIO_ENCODING_SLINEAR_LE,
+               .validbits              = 16,
+               .precision              = 16,
+               .channels               = 1,
+               .channel_mask           = AUFMT_MONAURAL,
+               .frequency_type         = 1,
+
+               /* Keep this in sync with pcmaudio_sampling_rate above */
+               .frequency              = {22000}
+       }
+};
+
+static const struct audio_hw_if pcmaudio_hw_if = {
+       .open                   = pcmaudio_open,
+       .close                  = pcmaudio_close,
+       .drain                  = NULL,
+       .query_encoding         = pcmaudio_query_encoding,
+       .set_params             = pcmaudio_set_params,
+       .round_blocksize        = pcmaudio_round_blocksize,
+       .commit_settings        = NULL,
+       .init_output            = NULL,
+       .init_input             = NULL,
+       .start_output           = NULL,
+       .start_input            = NULL,
+       .halt_output            = pcmaudio_halt_output,
+       .halt_input             = pcmaudio_halt_input,
+       .speaker_ctl            = pcmaudio_speaker_ctl,
+       .getdev                 = pcmaudio_getdev,
+       .setfd                  = NULL,
+
+       .set_port               = pcmaudio_set_port,
+       .get_port               = pcmaudio_get_port,
+       .query_devinfo          = pcmaudio_query_devinfo,
+
+       /* Memory allocation handled by audio(4) */
+       .allocm                 = NULL,
+       .freem                  = NULL,
+       .round_buffersize       = pcmaudio_round_buffersize,
+       .mappage                = pcmaudio_mappage,
+       .get_props              = pcmaudio_get_props,
+       .trigger_output         = pcmaudio_trigger_output,
+       .trigger_input          = pcmaudio_trigger_input,
+       .dev_ioctl              = NULL,
+       .powerstate             = NULL,
+};
+
+CFATTACH_DECL_NEW(pcmaudio, sizeof(struct pcmaudio_softc),
+    pcmaudio_match, pcmaudio_attach, pcmaudio_detach, NULL);
+
+int
+pcmaudio_match(device_t parent, cfdata_t match, void *aux)
+{
+       return 1;
+}
+
+/* Attachment is a bit complicated because we have in effect, two
+ * parent devices  (pcppi(4) and attimer(4)).
+ * We therefore defer the audio(4) attachment to
+ * pcmaudio_pcppi_attach() See below:
+ * This ensures that both parents are attached, failing which we do
+ * not attach the driver to the audio subsystem.
+ */
+
+void
+pcmaudio_attach(device_t parent, device_t self, void *aux)
+{
+
+       struct pcppi_softc *ppi_sc = device_private(parent);
+       struct pcmaudio_softc *sc = device_private(self);
+
+       /*
+        * Global variable shim, because there can only be one
+        * IO_TIMER1 on isa busses
+        */
+       pcmaudio_softc = sc;
+
+       ppi_sc->sc_pcmaudiodev = self;
+
+       /* Initialise some softc members to default values */
+       sc->sc_audiodev = NULL;
+       sc->sc_open = 0;
+       sc->sc_dev = self;
+       sc->sc_samplingrate = hz; /* This is set to sampling rate on open(). */
+       sc->sc_spkrvolume = AUDIO_MAX_GAIN / 2;
+
+       /* Watch for power change */
+       if (!pmf_device_register(sc->sc_dev, NULL, NULL))
+               aprint_error_dev(sc->sc_dev, "couldn't establish power handler\n");
+
+       aprint_naive(": XT PC Speaker driven with PCM @ %dHz\n", pcmaudio_sampling_rate);
+       aprint_normal(": XT PC Speaker driven with PCM @ %dHz\n", pcmaudio_sampling_rate);
+
+
+}
+
+int
+pcmaudio_detach(device_t self, int flags)
+{
+       pmf_device_deregister(self);
+       return 0;
+}
+
+
+void
+pcmaudio_pcppi_attach(device_t pcppidev)
+{
+       struct pcppi_softc *ppi_sc = device_private(pcppidev);
+       struct attimer_softc *attimer_sc = device_private(ppi_sc->sc_timer);
+       struct pcmaudio_softc *sc = device_private(ppi_sc->sc_pcmaudiodev);
+
+       const struct sysctlnode *node, *node_samplingrate;
+       int err, node_pcmaudio;
+
+       if (attimer_sc != NULL && (attimer_sc->sc_flags & ATT_ATTACHED)){
+
+               /* Final updates to the pcmaudio sc struct */
+               sc->sc_ppidev = pcppidev;
+               sc->sc_timerdev = ppi_sc->sc_timer;
+
+               aprint_normal_dev(ppi_sc->sc_pcmaudiodev, "attached to %s and %s\n",
+                                 device_xname(pcppidev),
+                                 device_xname(ppi_sc->sc_timer));
+
+
+               /* auconv encodings */
+               memcpy(sc->sc_audio_formats, pcmaudio_formats, sizeof(pcmaudio_formats));
+               if (auconv_create_encodings(sc->sc_audio_formats, PCMAUDIO_NFORMATS,
+                                           &sc->sc_encodings) != 0){
+                       return;
+               }
+
+               /* If all is well, attach to the audio subsystem */
+               sc->sc_audiodev = audio_attach_mi(&pcmaudio_hw_if, sc, sc->sc_dev);
+
+
+               /* Kickstart the 8254. */
+               initrtclock(TIMER_FREQ);
+
+               /* If the main system timer is not the 8254, it has not been
+                * programmed to interrupt. See: x86/x86/lapic.c
+                * Do it now.
+                */
+               if (sc->sc_timecounter->tc_priv == NULL) {
+                       /* Register the interrupt handler, and enable
+                        * interrupts.
+                        */
+                       i8254_initclocks();
+               }
+
+
+               /* sysctl nodes */
+               err = sysctl_createv(&sc->sc_log, 0, NULL, NULL, 0,
+                                    CTLTYPE_NODE, "hw", NULL, NULL, 0, NULL, 0,
+                                    CTL_HW, CTL_EOL);
+               if (err != 0)
+                       goto sysctl_err;
+               err = sysctl_createv(&sc->sc_log, 0, NULL, &node, 0,
+                                    CTLTYPE_NODE, device_xname(ppi_sc->sc_pcmaudiodev), NULL, NULL, 0,
+                                    NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL);
+               if (err != 0)
+                       goto sysctl_err;
+
+               node_pcmaudio = node->sysctl_num;
+
+               err = sysctl_createv(&sc->sc_log, 0, NULL, &node_samplingrate,
+                                    CTLFLAG_READWRITE,
+                                    CTLTYPE_INT, "samplingrate",
+                                    SYSCTL_DESCR("sampling rate (the 8254 interrupts at this rate)"),
+                                    pcmaudio_sysctl_verify, 0, sc, 0,
+                                    CTL_HW, node_pcmaudio, CTL_CREATE, CTL_EOL);
+               if (err != 0)
+                       goto sysctl_err;
+
+               sc->sc_sysctlnode = node_samplingrate->sysctl_num;
+
+               return;
+
+       sysctl_err:
+               printf("%s: failed to add sysctl nodes. (%d)\n",
+                      device_xname(ppi_sc->sc_pcmaudiodev), err);
+               return;                 /* failure of sysctl is not fatal. */
+       }
+}
+
+static int
+pcmaudio_sysctl_verify(SYSCTLFN_ARGS)
+{
+       int error, tmp;
+       struct sysctlnode node;
+       struct pcmaudio_softc *sc;
+
+       node = *rnode;
+       sc = rnode->sysctl_data;
+       if (node.sysctl_num == sc->sc_sysctlnode) {
+               tmp = pcmaudio_sampling_rate;
+               node.sysctl_data = &tmp;
+               error = sysctl_lookup(SYSCTLFN_CALL(&node));
+               if (error || newp == NULL)
+                       return error;
+
+               if (tmp < 8000 || tmp > 44000)
+                       return EINVAL;
+               pcmaudio_sampling_rate = tmp;
+       }
+
+       return 0;
+}
+
+
+static int
+pcmaudio_open(void *addr, int flags)
+{
+       struct pcmaudio_softc *sc = addr;
+
+       if (!sc) {
+               return ENXIO;
+       }
+       if (sc->sc_open & FREAD) {
+               return EBUSY;
+       }
+
+       if (flags & FREAD) {
+               return ENXIO;
+       }
+
+       sc->sc_open |= FREAD;
+
+       /* Sync up with sampling rate, if required. */
+       if (sc->sc_samplingrate != pcmaudio_sampling_rate) {
+
+               sc->sc_samplingrate = pcmaudio_sampling_rate;
+
+               /* We need to re-do auconv filters if the sampling rate
+                * changes.
+                */
+
+               if (auconv_delete_encodings(sc->sc_encodings)) {
+                       return ENXIO;
+               }
+
+               sc->sc_audio_formats[PCMAUDIO_NFORMATS - 1].frequency[0] = sc->sc_samplingrate;
+
+               if (auconv_create_encodings(sc->sc_audio_formats, PCMAUDIO_NFORMATS,
+                                           &sc->sc_encodings) != 0){
+                       return ENXIO;
+               }
+
+               /* Crank up the 8254 to sampling rate. */
+               initrtclock(TIMER_FREQ);
+       }
+
+       return 0;
+}
+
+static void
+pcmaudio_close(void *addr)
+{
+
+       struct pcmaudio_softc *sc = addr;
+
+       sc->sc_open &= ~FREAD;
+
+       /* Switch off speaker */
+       pcmaudio_speaker_ctl(addr, SPKR_OFF);
+
+       /* Minimise the 8254 interrupt rate when we're not using it
+        * for playback.
+        */
+
+       sc->sc_samplingrate = hz;
+
+       if (timecounter != sc->sc_timecounter) {
+               initrtclock(0);
+               return;
+       }
+
+
+       initrtclock(TIMER_FREQ);
+
+       return;
+
+}
+
+/* query_encoding: return current h/w encoding in
+ * audio_encoding_t *encp;
+ *
+ * INPUTS: addr -> points to the device sc, encp->index points to the
+ *        index number of the encoding we're interested in.
+ *
+ * OUTPUTS: *encp is filled in with the current device encoding
+ *
+ * RETURNS: 0 on success, EINVAL on unsupported encoding index.
+ */
+
+static int
+pcmaudio_query_encoding(void *addr, audio_encoding_t *encp)
+{
+
+       struct pcmaudio_softc *sc = addr;
+
+       return auconv_query_encoding(sc->sc_encodings, encp);
+}
+
+
+/*
+ * set_params: set the hardware to operate under specified params.
+ *
+ * INPUTS: addr-> points to device sc,
+ *        setmode is a subset of AUMODE_PLAY | AUMODE_RECORD,
+ *        usemode is the current mode ( also a subset of above )
+ *        pparm is the requisite audio_params to be set ( encoding,
+ *        precision, etc. ) for playback.
+ *        rparm is as pparm, but for recording.
+ *        pfil and rfil are stream filter hooks to be added for
+ *        particular modes.
+ *
+ * OUTPUTS: None. Just update the h/w and return 0 if all goes well.
+ *
+ * RETURNS: return 0 if all is well. If an unsupported parameter is
+ *         requested, return EINVAL.
+ */
+
+
+static int
+pcmaudio_set_params(void *addr, int setmode, int usemode, audio_params_t *pparm,
+                   audio_params_t *rparm, stream_filter_list_t *pfil,
+                   stream_filter_list_t *rfil)
+{
+       struct pcmaudio_softc *sc = addr;
+
+       int index;
+
+       /* We don't support AUMODE_RECORD */
+       /* Bail out, if AUMODE_PLAY is not asked for. */
+       /* Note that we silently ignore the "record" aspect of
+        * AUMODE_PLAY | AUMODE_RECORD
+        */
+
+       if (setmode & AUMODE_RECORD &&
+           !(setmode & AUMODE_PLAY)) {
+               return EINVAL;
+       }
+
+       index = auconv_set_converter(sc->sc_audio_formats, PCMAUDIO_NFORMATS,
+                                    setmode, pparm, TRUE, pfil);
+
+       if (index < 0) {
+               return EINVAL;
+       }
+
+       return 0;
+
+}
+
+/*
+ * round_blocksize: A block is a DMA-able unit of memory. Some DMA h/w
+ *                 have alignment and size constraints, which are
+ *                 implemented via this hook.
+ * INPUTS:         'addr' is the h/w sc, 'blksize' is the current
+ *                 blocksize that will be requested. 'mode' is the
+ *                 current h/w mode ( AUMODE_PLAY | AUMODE_RECORD),
+ *                 'param' is the current param settings of h/w.
+ *
+ * OUTPUTS/RETURNS:    The rounded down size of a block of memory.
+ */
+
+static int
+pcmaudio_round_blocksize(void *addr, int blksize, int mode,
+                        const audio_params_t *param)
+{
+       /* Our alignment constraints are slim. Since we aim to support
+        * 32bit linear PCM, we have block sizes which are a multiple
+        * of 4bytes, for now.
+        */
+
+       return blksize & -4;
+
+}
+
+
+/*
+ * halt_output:            Stop playing _now_
+ *
+ * INPUTS:         'addr' is the h/w sc
+ *
+ * OUTPUTS/RETURNS: 0 on success.
+ */
+
+static int
+pcmaudio_halt_output(void *addr)
+{
+       struct pcmaudio_softc *sc = addr;
+
+       /* Switch off speaker */
+       pcmaudio_speaker_ctl(addr, SPKR_OFF);
+
+       /* Flag the interrupt handler. */
+       sc->playing = false;
+
+       return 0;
+
+}
+
+/*
+ * halt_input:     Stop recording _now_
+ *
+ * INPUTS:         'addr' is the h/w sc
+ *
+ * OUTPUTS/RETURNS: ENXIO. We don't support record.
+ */
+
+static int
+pcmaudio_halt_input(void *addr)
+{
+
+       /* We don't support input */
+       return ENXIO;
+}
+
+
+/*
+ * speaker_ctl:                Switch the speaker on/off
+ *
+ * INPUTS:             'addr' is the h/w sc.
+ *                     spkr_switch is SPKR_ON or SPKR_OFF
+ *
+ * OUTPUTS/RETURNS:    0 on success. EINVAL on invalid 'spkr_switch'.
+ */
+
+static int
+pcmaudio_speaker_ctl(void *addr, int spkr_switch)
+{
+       struct pcmaudio_softc *sc = addr;
+
+       struct pcppi_softc *ppisc = device_private(sc->sc_ppidev);
+
+       switch (spkr_switch) {
+       case SPKR_ON:
+               /* enable speaker */
+               bus_space_write_1(ppisc->sc_iot, ppisc->sc_ppi_ioh, 0,
+                                 bus_space_read_1(ppisc->sc_iot, ppisc->sc_ppi_ioh, 0)
+                                 | PIT_SPKR);
+               break;
+       case SPKR_OFF:
+               bus_space_write_1(ppisc->sc_iot, ppisc->sc_ppi_ioh, 0,
+                                 bus_space_read_1(ppisc->sc_iot, ppisc->sc_ppi_ioh, 0)
+                                 & ~PIT_SPKR);
+               break;
+       default:
+               return EINVAL;
+       }
+               /* XXX: Untested. Remove me when done */
+       return 0;
+}
+
+/*
+ * getdev:     Return information about the pcmaudio(4) driver.
+ *
+ * INPUTS:     'addr' is the h/w sc
+ *             'adev' is filled with descriptive information
+ *
+ * RETURNS:    0 on success
+ *
+ */
+
+static int
+pcmaudio_getdev(void *addr, struct audio_device *adev)
+{
+
+       struct audio_device pcmaudiodev = { "PC Speaker (PCM)",
+                                           "0.1",
+                                           "pcmaudio"
+       };
+
+       *adev = pcmaudiodev;
+       return 0;
+}
+
+/* Mixer */
+
+enum {
+       PCMAUDIO_SPKRCLASS,
+       PCMAUDIO_SPKRVOLUME
+};
+
+/*
+ * set_port:           Set the volume.
+ *
+ * INPUTS:             'addr' is the h/w sc
+ *                     'mxc' contains volume information.
+ *
+ * OUTPUTS:            EINVAL/0 on invalid input/success.
+ */
+
+static int
+pcmaudio_set_port(void *addr, mixer_ctrl_t *mxc)
+{
+
+       struct pcmaudio_softc *sc = addr;
+
+       if (mxc->dev == PCMAUDIO_SPKRVOLUME) {
+               mxc->type = AUDIO_MIXER_VALUE;
+               mxc->un.value.num_channels = 1;
+               sc->sc_spkrvolume = mxc->un.value.level[AUDIO_MIXER_LEVEL_MONO];
+               return 0;
+       }
+
+
+       return EINVAL;
+}
+
+/*
+ * set_port:           Set the volume.
+ *
+ * INPUTS:             'addr' is the h/w sc
+ *                     'mxc' will be filled in with volume
+ *                     information.
+ *
+ * OUTPUTS:            EINVAL/0 on invalid input/success.
+ *                     volume info, via 'mxc'
+ */
+
+static int
+pcmaudio_get_port(void *addr, mixer_ctrl_t *mxc)
+{
+
+       struct pcmaudio_softc *sc = addr;
+
+       if (mxc->dev == PCMAUDIO_SPKRVOLUME) {
+               mxc->type = AUDIO_MIXER_VALUE;
+               mxc->un.value.num_channels = 1;
+               mxc->un.value.level[AUDIO_MIXER_LEVEL_MONO] = sc->sc_spkrvolume;
+               return 0;
+       }
+
+       return EINVAL;
+}
+
+/*
+ * query_devinfo:      Pass up mixer information to the audio(4)
+ *                     layer.
+ *
+ * INPUTS:             'addr' is the h/w sc
+ *                     'mxd' is the pointer to output to mixer info
+ *
+ * OUTPUTS/RETURNS:    'mxd' is filled in with mixer info.
+ *                     0 on success, error, otherwise.
+ */
+
+static int
+pcmaudio_query_devinfo(void *addr, mixer_devinfo_t *mxd)
+{
+
+       switch(mxd->index) {
+       case PCMAUDIO_SPKRCLASS:
+               mxd->type = AUDIO_MIXER_CLASS;
+               mxd->mixer_class = PCMAUDIO_SPKRCLASS;
+               strcpy(mxd->label.name, AudioCoutputs);
+               mxd->next = mxd->prev = AUDIO_MIXER_LAST;
+               break;
+
+       case PCMAUDIO_SPKRVOLUME:
+               mxd->type = AUDIO_MIXER_VALUE;
+               mxd->mixer_class = PCMAUDIO_SPKRCLASS;
+               strcpy(mxd->label.name, AudioNspeaker);
+               strcpy(mxd->un.v.units.name, AudioNvolume);
+               mxd->un.v.num_channels = 1;
+               mxd->un.v.delta = PCM_MIXER_DELTA;
+               mxd->next = mxd->prev = AUDIO_MIXER_LAST;
+               break;
+
+       default:
+               /* We don't support mixers just yet. */
+               return ENXIO;
+       }
+
+       return 0;
+}
+
+/*
+ * round_buffersize: A block is a DMA-able unit of memory. Some DMA h/w
+ *                  have alignment and size constraints, which are
+ *                  implemented via this hook.
+ * INPUTS:          'addr' is the h/w sc, 'bufsize' is the current
+ *                  buffer size for the ring buffer in
+ *                  question. 'direction' is the mode (AUMODE_PLAY |
+ *                  AUMODE_RECORD) for which the ring buffer is
+ *                  specified.
+ *
+ * OUTPUTS/RETURNS:  The rounded down size of the ring buffer.
+ */
+
+static size_t
+pcmaudio_round_buffersize(void *addr, int direction, size_t bufsize)
+{
+
+       return bufsize & -4;
+
+}
+
+/*
+ * mappage:            Backend to map DMA memory to userland.
+ *
+ * INPUTS:             'addr' is the h/w sc. 'start' with 'foffset'
+ *                     is the userspace address  where the buffer
+ *                     mapping is requested.
+ *                     'prot' is the page protections of the mapping.
+ *
+ * OUTPUTS:            -1 on error. paddr of the mapping for the
+ *                     asking process, on success.
+ */
+
+static paddr_t
+pcmaudio_mappage(void *addr, void *start, off_t foffset, int prot)
+{
+       struct vm_map_entry *entry;
+       vsize_t mapoffset;
+       paddr_t pstart;
+
+       vm_map_lock(kernel_map);
+       if (uvm_map_lookup_entry(kernel_map, (vaddr_t) start, &entry) == false) {
+               return 0;
+       }
+
+       /* Confirm if the map we got is sane. */
+
+       if ((vaddr_t)start < entry->start) {
+               return -1;
+       }
+
+       /* The offset in bytes from the map, entry start */
+       mapoffset = ((vaddr_t)start - entry->start);
+
+       pstart = entry->start + mapoffset;
+       vm_map_unlock(kernel_map);
+
+       return pstart;
+}
+
+/*
+ * get_props:          'addr' is the h/w sc
+ *
+ * INPUTS:             None.
+ *
+ * OUTPUTS:            properties passed up to the audio(4) driver.
+ */
+
+static int
+pcmaudio_get_props(void *addr)
+{
+
+       return (AUDIO_PROP_MMAP);
+}
+
+/*
+ * INPUTS: 'addr' -> the h/w sc. 'start' is the start address.
+ *        'end' is the end address of the 'DMA' ring buffer.
+ *        'blksize' is the number of bytes to be played by this 'DMA'
+ *                  operation.
+ *         'intp' is called after each block is processed.
+ *        'aparams' contains a description of the nature of the
+ *         encoding of the audio data in the buffer.
+ *
+ * OUTPUTS: none.
+ *
+ * RETURNS: 0 on success.
+ *
+ */
+
+static         int pcmaudio_trigger_output(void *addr, void *start, void *end, int blksize,
+                                   void (*intp)(void *), void *arg, const audio_params_t *aparams)
+{
+
+       struct pcmaudio_softc *sc = addr;
+
+       if (start >= end) {
+               return EINVAL;
+       }
+
+
+       /* XXX: Set the PCM sampling rate */
+
+       if (sc->sc_pintr != intp ||
+           sc->sc_parg != arg) {
+               sc->sc_pintr = intp;
+               sc->sc_parg = arg;
+
+               /* softint */
+               sc->sc_softintcookie = softint_establish(SOFTINT_CLOCK, intp, arg);
+
+       }
+
+       sc->blksize = blksize;
+       sc->blknow = sc->blkstart = start;
+       sc->blkend = end;
+       sc->playing = true;
+
+       return 0;
+}
+
+/*
+ * INPUTS: 'addr' -> the h/w sc. 'start' is the start address.
+ *        'end' is the end address of the 'DMA' ring buffer.
+ *        'blksize' is the number of bytes to be played by this 'DMA'
+ *                  operation.
+ *         'intp' is called after each block is processed.
+ *        'aparams' contains a description of the nature of the
+ *         encoding of the audio data in the buffer.
+ *
+ * OUTPUTS: none.
+ *
+ * RETURNS: 0 on success.
+ *
+ */
+
+static         int pcmaudio_trigger_input(void *addrl, void *start, void *end, int blksize,
+                                void (*intp)(void *), void *arg, const audio_params_t *aparams)
+{
+       /* Nope, we don't support capture */
+
+       return ENXIO;
+}
Index: sys/dev/isa/pcmaudiovar.h
===================================================================
RCS file: sys/dev/isa/pcmaudiovar.h
diff -N sys/dev/isa/pcmaudiovar.h
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ sys/dev/isa/pcmaudiovar.h   19 Sep 2008 13:51:18 -0000
@@ -0,0 +1,68 @@
+/*     $NetBSD$         */
+
+/*
+ * pcmaudiovar.h: definitions specific to the pcmaudio driver.
+ */
+
+#ifndef _DEV_ISA_PCMAUDIOVAR_H_
+#define _DEV_ISA_PCMAUDIOVAR_H_
+
+#ifndef NSPKR
+#include "spkr.h"
+#endif /* NSPKR  */
+/* The pcmaudio driver is exclusive to the spkr tone driver: "spkr at pcppi" */
+#if NSPKR > 0
+#error spkr at pcppi, and pcmaudio at pcppi are mutually exclusive. Please check your config file.
+#else
+
+#include <dev/auconv.h>
+#include <sys/timetc.h>
+
+#define PCM_MIXER_DELTA 16
+
+struct pcmaudio_softc {
+       device_t sc_dev;        /* pcmaudio device instance struct */
+       device_t sc_audiodev;   /* audio(4) device instance struct which attaches to us */
+
+       device_t sc_ppidev;     /* Parent pcppi device */
+       device_t sc_timerdev;   /* Parent 8254 dev */
+
+       int sc_open;            /* We don't support multiple opens */
+       int sc_samplingrate;    /* Only updated at open() */
+       u_char sc_spkrvolume;   /* Mixer volume */
+
+       bool playing;           /* Flag, polled by clock.c:clockintr() */
+
+       int blksize;            /* Size of the DMA circular buffer */
+       int16_t *blkstart;      /* Pointer to the start of the buffer */
+       int16_t *blknow;        /* Pointer to current sample */
+       int16_t *blkend;        /* Pointer to the end of the buffer */
+
+
+       void (*sc_pintr)(void *);       /* Callback from audio(4) */
+       void *sc_parg;          /* The argument that the audio callback is called with. see audio_if.h */
+
+       void *sc_softintcookie; /* Cookie returned by softint_establish() */
+
+       struct timecounter *sc_timecounter; /* Pointer to the i8254 timer */
+
+#define PCMAUDIO_NFORMATS 1
+
+       /* auconv encoding related */
+       struct audio_format sc_audio_formats[PCMAUDIO_NFORMATS];
+       struct audio_encoding_set *sc_encodings;
+
+       struct sysctllog *sc_log; /* sysctl related */
+       int sc_sysctlnode;
+
+};
+
+int    pcmaudio_match(device_t, cfdata_t, void *);
+void   pcmaudio_attach(device_t, device_t, void *);
+int    pcmaudio_detach(device_t, int);
+
+void   pcmaudio_pcppi_attach(device_t);
+
+
+#endif /* NSPKR */
+#endif /* _DEV_ISA_PCMAUDIOVAR_H_ */
Index: sys/dev/isa/pcppi.c
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/dev/isa/pcppi.c,v
retrieving revision 1.32
diff -u -r1.32 pcppi.c
--- sys/dev/isa/pcppi.c 5 Mar 2008 22:46:43 -0000       1.32
+++ sys/dev/isa/pcppi.c 19 Sep 2008 08:13:45 -0000
@@ -48,6 +48,11 @@
#include <dev/isa/isavar.h>
#include <dev/isa/pcppireg.h>
#include <dev/isa/pcppivar.h>
+#include "pcmaudio.h"
+
+#if NPCMAUDIO > 0
+#include <dev/isa/pcmaudiovar.h>
+#endif

#include "pckbd.h"
#if NPCKBD > 0
@@ -207,7 +212,7 @@

        sc->sc_bellactive = sc->sc_bellpitch = sc->sc_slp = 0;

-#if NPCKBD > 0
+#if ((NPCKBD > 0) && (NPCMAUDIO == 0))
       /* Provide a beeper for the PC Keyboard, if there isn't one already. */
       pckbd_hookup_bell(pcppi_pckbd_bell, sc);
#endif
@@ -244,6 +249,9 @@
       else {
               aprint_normal_dev(sc->sc_timer, "attached to %s\n",
                   device_xname(self));
+#if NPCMAUDIO > 0
+               pcmaudio_pcppi_attach(self);
+#endif
       }
}
#endif
Index: sys/dev/isa/pcppivar.h
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/dev/isa/pcppivar.h,v
retrieving revision 1.9
diff -u -r1.9 pcppivar.h
--- sys/dev/isa/pcppivar.h      4 Mar 2008 16:35:19 -0000       1.9
+++ sys/dev/isa/pcppivar.h      18 Jul 2008 11:33:36 -0000
@@ -49,6 +49,11 @@
        int sc_bellactive, sc_bellpitch;
        int sc_slp;
        int sc_timeout;
+
+#if NPCMAUDIO > 0
+       device_t sc_pcmaudiodev;
+#endif
+
};

void pcppi_attach(struct pcppi_softc *);