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	9 Jan 2009 01:02:40 -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	9 Jan 2009 16:14:11 -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/arch/i386/conf/GENERIC
===================================================================
RCS file: /home/repos/netbsd-current/src/sys/arch/i386/conf/GENERIC,v
retrieving revision 1.922
diff -u -r1.922 GENERIC
--- sys/arch/i386/conf/GENERIC	28 Dec 2008 15:18:21 -0000	1.922
+++ sys/arch/i386/conf/GENERIC	16 Jan 2009 23:36:27 -0000
@@ -1317,7 +1317,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)
+#audio*  at pcppi?		#with pwmaudio? 
 
 # 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.31
diff -u -r1.31 clock.c
--- sys/arch/x86/isa/clock.c	16 Dec 2008 22:35:28 -0000	1.31
+++ sys/arch/x86/isa/clock.c	16 Jan 2009 00:26:16 -0000
@@ -175,6 +175,16 @@
 static pcppi_tag_t ppicookie;
 #endif /* PCPPI */
 
+#include "pwmaudio.h"
+#if NPWMAUDIO > 0
+#include <dev/isa/pwmaudiovar.h>
+struct pwmaudio_softc *pwmaudio_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 NPWMAUDIO > 0
+	struct pwmaudio_softc *sc = pwmaudio_softc;
+	int samplingrate;
+
+	if (sc == NULL) { /* Before pwmaudio(4) is attached. */
+		samplingrate = hz;
+	}
+	else { /* After pwmaudio(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 */
@@ -402,7 +437,60 @@
 {
 	tickle_tc();
 
-	hardclock((struct clockframe *)frame);
+#if NPWMAUDIO > 0
+
+	struct pwmaudio_softc *sc = pwmaudio_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);
+
+			/* Adjust volume */
+			tval = tval * sc->sc_spkrvolume / AUDIO_MAX_GAIN;
+
+			tval |= 0x1; /* (1 < tval < rtclock_tval) */
+
+			/* 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);
+
+			if ((((size_t)sc->blknow - (size_t)sc->blkstart) % sc->blksize) == 0) {
+				/* End of block reached: schedule the audio(4) callback */
+				softint_schedule(sc->sc_softintcookie);
+				
+			}
+		}
+		else {
+			/* reset the blk pointers */
+			sc->blknow = sc->blkstart;
+		}
+
+	}
+
+	if (hardskip < tvaldiv){
+		hardskip++;
+		goto eoi;
+	}
+	
+	hardskip = 0;
+
+#endif
+
+	/* 
+	 * We only handle the system clock if we're really in charge
+	 * of it. XXX: poor API :XXX.
+	 */
+	if (initclock_func == i8254_initclocks) {
+		hardclock((struct clockframe *)frame);
+	}
+
+#if NPWMAUDIO > 0 
+eoi:
+#endif
 
 #if NMCA > 0
 	if (MCA_system) {
@@ -410,6 +498,7 @@
 		outb(0x61, inb(0x61) | 0x80);
 	}
 #endif
+
 	return -1;
 }
 
@@ -575,13 +664,12 @@
 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/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	16 Jan 2009 02:05:09 -0000
@@ -443,6 +443,8 @@
 file	dev/isa/spkr.c			spkr			needs-flag
 attach	midi at pcppi with midi_pcppi: midisyn
 file	dev/isa/midi_pcppi.c		midi_pcppi
+attach  audio at pcppi with pwmaudio: auconv, mulaw, aurateconv
+file	dev/isa/pwmaudio.c		pwmaudio		needs-flag
 
 # AT Timer (TIMER 1)
 attach	attimer at isa with attimer_isa
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	14 Jan 2009 23:16:49 -0000
@@ -48,6 +48,7 @@
 #include <dev/isa/isavar.h>
 #include <dev/isa/pcppireg.h>
 #include <dev/isa/pcppivar.h>
+#include "pwmaudio.h"
 
 #include "pckbd.h"
 #if NPCKBD > 0
@@ -207,7 +208,7 @@
 
         sc->sc_bellactive = sc->sc_bellpitch = sc->sc_slp = 0;
 
-#if NPCKBD > 0
+#if ((NPCKBD > 0) && (NPWMAUDIO == 0))
 	/* Provide a beeper for the PC Keyboard, if there isn't one already. */
 	pckbd_hookup_bell(pcppi_pckbd_bell, sc);
 #endif
Index: sys/dev/isa/pwmaudio.c
===================================================================
RCS file: sys/dev/isa/pwmaudio.c
diff -N sys/dev/isa/pwmaudio.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sys/dev/isa/pwmaudio.c	16 Jan 2009 23:23:59 -0000
@@ -0,0 +1,889 @@
+/* 	$NetBSD$	 */
+
+/*-
+ * Copyright (c) 2008 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Written by Cherry G. Mathew <cherry@zyx.in> 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/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD$");
+
+#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/audiovar.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 "pwmaudio.h"
+
+#include <dev/isa/pwmaudiovar.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 pwmaudio_open(void *, int);
+static void pwmaudio_close(void *);
+static int pwmaudio_query_encoding(void *, audio_encoding_t *);
+static int pwmaudio_set_params(void *, int, int, audio_params_t *,
+			       audio_params_t *, stream_filter_list_t *,
+			       stream_filter_list_t *);
+static int pwmaudio_round_blocksize(void *, int, int, const audio_params_t *);
+static int pwmaudio_halt_output(void *);
+static int pwmaudio_halt_input(void *);
+
+static int pwmaudio_speaker_ctl(void *, int);
+static int pwmaudio_getdev(void *, struct audio_device *);
+
+/* Mixer functions. Stubs for now. */
+static int pwmaudio_set_port(void *, mixer_ctrl_t *);
+static int pwmaudio_get_port(void *, mixer_ctrl_t *);
+
+static int pwmaudio_query_devinfo(void *, mixer_devinfo_t *);
+
+
+static size_t pwmaudio_round_buffersize(void *, int, size_t);
+static paddr_t pwmaudio_mappage(void *, void *, off_t, int);
+static int pwmaudio_get_props(void *);
+static 	int pwmaudio_trigger_output(void *, void *, void *, int, void (*)(void *),
+				    void *, const audio_params_t *);
+static 	int pwmaudio_trigger_input(void *, void *, void *, int, void (*)(void *),
+				   void *, const audio_params_t *);
+static int pwmaudio_sysctl_verify(SYSCTLFN_ARGS);
+
+extern struct pwmaudio_softc *pwmaudio_softc;
+
+/* 
+ * We update the actual sampling rate only after an open/close pair.
+ * This value caches the sampling rate until the next open/close.
+ */
+static int pwmaudio_sampling_rate = 22000;
+
+/* H/W driver info */
+struct audio_device pwmaudiodev = { "PC Speaker (PWM)",
+				    "0.2",
+				    "pwmaudio"
+};
+
+static const struct audio_format pwmaudio_formats[PWMAUDIO_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 pwmaudio_sampling_rate above */  
+		.frequency		= {22000} 
+	}
+};
+
+static const struct audio_hw_if pwmaudio_hw_if = {
+	.open			= pwmaudio_open,
+	.close			= pwmaudio_close,
+	.drain			= NULL,
+	.query_encoding		= pwmaudio_query_encoding,
+	.set_params		= pwmaudio_set_params,
+	.round_blocksize	= pwmaudio_round_blocksize,
+	.commit_settings	= NULL,
+	.init_output		= NULL,
+	.init_input		= NULL,
+	.start_output		= NULL,
+	.start_input		= NULL,
+	.halt_output		= pwmaudio_halt_output,
+	.halt_input		= pwmaudio_halt_input,
+	.speaker_ctl		= pwmaudio_speaker_ctl,
+	.getdev			= pwmaudio_getdev,
+	.setfd			= NULL,
+
+	.set_port		= pwmaudio_set_port,
+	.get_port		= pwmaudio_get_port,
+	.query_devinfo		= pwmaudio_query_devinfo,
+
+	/* Memory allocation handled by audio(4) */
+	.allocm			= NULL,
+	.freem			= NULL,
+	.round_buffersize	= pwmaudio_round_buffersize,
+	.mappage		= pwmaudio_mappage,
+	.get_props		= pwmaudio_get_props,
+	.trigger_output		= pwmaudio_trigger_output,
+	.trigger_input		= pwmaudio_trigger_input,
+	.dev_ioctl		= NULL,
+	.powerstate		= NULL,
+};
+
+CFATTACH_DECL_NEW(pwmaudio, sizeof(struct pwmaudio_softc),
+    pwmaudio_match, pwmaudio_attach, pwmaudio_detach, NULL);
+
+int
+pwmaudio_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
+ * pwmaudio_pcppi_attach() See below:
+ * This ensures that both parents are attached, failing which we do
+ * not attach the driver to the audio subsystem.
+ */
+
+void
+pwmaudio_attach(device_t parent, device_t self, void *aux)
+{
+	
+	struct pcppi_softc *ppi_sc;
+	struct pwmaudio_softc *sc;
+
+	ppi_sc = device_private(parent);
+	sc = device_private(self);
+
+	/* 
+	 * Global variable shim, because there can only be one
+	 * IO_TIMER1 on isa busses
+	 */
+	pwmaudio_softc = sc;
+
+	/* Initialise some softc members to default values */
+	sc->sc_ppi = ppi_sc;
+	sc->sc_open = 0;
+	sc->sc_samplingrate = hz; /* This is set to sampling rate on open(). */
+	sc->sc_spkrvolume = AUDIO_MAX_GAIN / 2;
+
+	aprint_normal(": XT PC Speaker driven with PWM\n";
+
+	/* 
+	 * We need to defer config until all devices have been
+	 * attached, to make sure that the pcppi is tied to at least
+	 * one attimer.
+	 */
+
+	config_finalize_register(self, pwmaudio_pcppi_attach);	
+}
+
+int
+pwmaudio_detach(device_t self, int flags)
+{
+	struct pwmaudio_softc *sc;
+
+	sc = device_private(self);
+
+	/* Unregister auconv encodings. */
+	if (sc->sc_encodings && auconv_delete_encodings(sc->sc_encodings)) {
+		return ENXIO;
+	}
+
+	/* shutdown last registered  (via pwmaudio_trigger) softint */
+
+	if (sc->sc_softintcookie != NULL) {
+		softint_disestablish(sc->sc_softintcookie);
+		sc->sc_softintcookie = NULL;
+	}
+
+	return 0;
+}
+
+extern void	audioattach(device_t, device_t, void *); /* XXX: Ugly force export from audio.c */
+
+/* This function is called by the deferred attachment from pcppi. It
+ * initialises the audio softc, before passing control to the audio
+ * attach.
+ */
+
+int
+pwmaudio_pcppi_attach(device_t self)
+{
+
+	device_t parent;
+	struct pcppi_softc *ppi_sc;
+	struct attimer_softc *attimer_sc;
+	struct pwmaudio_softc *sc;
+	struct audio_softc *asc;
+
+
+	const struct sysctlnode *node, *node_samplingrate;
+	int err, node_pwmaudio;
+
+	parent = device_parent(self);
+	ppi_sc = device_private(parent);
+	attimer_sc = device_private(ppi_sc->sc_timer);
+	sc = device_private(self);
+	asc = &sc->audio_sc;
+
+	/* We don't attach twice. */
+	if (sc->sc_sysctlnode != 0){
+		return 0;
+	}
+
+	if ((attimer_sc == NULL) || !(attimer_sc->sc_flags & ATT_ATTACHED)){
+		aprint_error_dev(self,
+				 "Skipping attach to %s - couldn't find associated timer.\n",
+				 device_xname(parent));
+		return 0;
+	}
+
+	aprint_normal_dev(self, "%s initialised to interrupt at @ %dHz while playing",
+			  device_xname(ppi_sc->sc_timer),
+			  pwmaudio_sampling_rate);
+	
+	/* auconv encodings */
+	memcpy(sc->sc_audio_formats, pwmaudio_formats, sizeof(pwmaudio_formats));
+	if (auconv_create_encodings(sc->sc_audio_formats, PWMAUDIO_NFORMATS,
+				    &sc->sc_encodings) != 0){
+		return 0;
+	}
+	
+	/* If all is well, attach to the audio subsystem */
+
+	/* Switch off speaker */
+	pwmaudio_speaker_ctl(sc, SPKR_OFF);
+
+	/* Update the audio_softc we service. */
+	asc->dev = self;
+	asc->sc_dev = parent;
+
+	asc->hw_if = &pwmaudio_hw_if;
+	asc->hw_hdl = sc;
+
+	audio_attach(asc);
+	
+	/* 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(self), NULL, NULL, 0,
+			     NULL, 0, CTL_HW, CTL_CREATE, CTL_EOL);
+	if (err != 0)
+		goto sysctl_err;
+	
+	node_pwmaudio = 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)"),
+			     pwmaudio_sysctl_verify, 0, sc, 0,
+			     CTL_HW, node_pwmaudio, CTL_CREATE, CTL_EOL);
+	if (err != 0)
+		goto sysctl_err;
+	
+	sc->sc_sysctlnode = node_samplingrate->sysctl_num;
+	
+	return 0;
+	
+sysctl_err:
+	aprint_error_dev(self,
+			 "failed to add sysctl nodes. (%d)\n", err);
+	return 0;	/* failure of sysctl is not fatal. */
+}
+
+
+static int
+pwmaudio_sysctl_verify(SYSCTLFN_ARGS)
+{
+	int error, tmp;
+	struct sysctlnode node;
+	struct pwmaudio_softc *sc;
+
+	node = *rnode;
+	sc = rnode->sysctl_data;
+	if (node.sysctl_num == sc->sc_sysctlnode) {
+		tmp = pwmaudio_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;
+		pwmaudio_sampling_rate = tmp;
+	}
+
+	return 0;
+}
+
+
+static int
+pwmaudio_open(void *addr, int flags)
+{
+	struct pwmaudio_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 != pwmaudio_sampling_rate) {
+	  
+		sc->sc_samplingrate = pwmaudio_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[PWMAUDIO_NFORMATS - 1].frequency[0] = sc->sc_samplingrate;
+		
+		if (auconv_create_encodings(sc->sc_audio_formats, PWMAUDIO_NFORMATS,
+					    &sc->sc_encodings) != 0){
+			return ENXIO;
+		}
+		
+	}
+
+	return 0;
+}
+
+static void
+pwmaudio_close(void *addr)
+{
+
+	struct pwmaudio_softc *sc = addr;
+	
+	sc->sc_open &= ~FREAD;
+
+	/* Switch off speaker */
+	pwmaudio_speaker_ctl(addr, SPKR_OFF);
+
+	/* Signal the clock.c:clockintr() to stop playing. */
+	sc->playing = false;
+
+	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
+pwmaudio_query_encoding(void *addr, audio_encoding_t *encp)
+{
+
+	struct pwmaudio_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
+pwmaudio_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 pwmaudio_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)) {
+		printf("setmode failed\n");
+		return EINVAL;
+	}
+
+	index = auconv_set_converter(sc->sc_audio_formats, PWMAUDIO_NFORMATS,
+				     setmode, pparm, TRUE, pfil);
+
+	if (index < 0) {
+		printf("set_convertor failed\n");
+		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
+pwmaudio_round_blocksize(void *addr, int blksize, int mode,
+			 const audio_params_t *param)
+{
+
+	/* Our alignment constraints are slim. Since we aim to support
+	 * 16bit linear PCM (see sc_audio_formats[]), we have block
+	 * sizes which are a multiple of 2 bytes, for now.
+	 */
+
+	return blksize & -2;
+
+}
+
+
+/*
+ * halt_output:	    Stop playing _now_ 
+ *
+ * INPUTS:	    'addr' is the h/w sc
+ *
+ * OUTPUTS/RETURNS: 0 on success.
+ */
+
+static int
+pwmaudio_halt_output(void *addr)
+{
+	struct pwmaudio_softc *sc = addr;
+
+	/* Flag the interrupt handler. */
+	sc->playing = false;
+
+	/* Bring down the 8254 interrupt rate when we're not using it
+	 * for playback. 
+	 */
+	{
+		sc->sc_samplingrate = hz;
+		if (timecounter != sc->sc_timecounter) {
+			initrtclock(0);
+			return 0;
+		}
+		initrtclock(TIMER_FREQ);
+	}
+
+	return 0;
+}
+
+/*
+ * halt_input:	    Stop recording _now_ 
+ *
+ * INPUTS:	    'addr' is the h/w sc
+ *
+ * OUTPUTS/RETURNS: ENXIO. We don't support record.
+ */
+
+static int
+pwmaudio_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
+pwmaudio_speaker_ctl(void *addr, int spkr_switch)
+{
+
+	struct pwmaudio_softc *sc = addr;
+	struct pcppi_softc *ppisc;
+
+	ppisc = sc->sc_ppi;
+
+	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;
+	}
+
+	return 0;
+}
+
+/*
+ * getdev:	Return information about the pwmaudio(4) driver.
+ *
+ * INPUTS:	'addr' is the h/w sc
+ *		'adev' is filled with descriptive information
+ *
+ * RETURNS:	0 on success
+ *
+ */
+
+static int
+pwmaudio_getdev(void *addr, struct audio_device *adev)
+{
+
+	*adev = pwmaudiodev;
+	return 0;
+}
+
+/* Mixer */
+
+enum {
+	PWMAUDIO_SPKRCLASS,
+	PWMAUDIO_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
+pwmaudio_set_port(void *addr, mixer_ctrl_t *mxc)
+{
+
+	struct pwmaudio_softc *sc = addr;
+
+	if (mxc->dev == PWMAUDIO_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
+pwmaudio_get_port(void *addr, mixer_ctrl_t *mxc)
+{
+
+	struct pwmaudio_softc *sc = addr;
+
+	if (mxc->dev == PWMAUDIO_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
+pwmaudio_query_devinfo(void *addr, mixer_devinfo_t *mxd)
+{
+
+	switch(mxd->index) {
+	case PWMAUDIO_SPKRCLASS:
+		mxd->type = AUDIO_MIXER_CLASS;
+		mxd->mixer_class = PWMAUDIO_SPKRCLASS;
+		strcpy(mxd->label.name, AudioCoutputs);
+		mxd->next = mxd->prev = AUDIO_MIXER_LAST;
+		break;
+
+	case PWMAUDIO_SPKRVOLUME:
+		mxd->type = AUDIO_MIXER_VALUE;
+		mxd->mixer_class = PWMAUDIO_SPKRCLASS;
+		strcpy(mxd->label.name, AudioNspeaker);
+		strcpy(mxd->un.v.units.name, AudioNvolume);
+		mxd->un.v.num_channels = 1;
+		mxd->un.v.delta = PWM_MIXER_DELTA;
+		mxd->next = mxd->prev = AUDIO_MIXER_LAST;
+		break;
+
+	default:
+		/* Unsupported mixer channel queried. */
+		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
+pwmaudio_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
+pwmaudio_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;
+	/* XXX: Untested. Remove me when done testing */
+}
+
+/*
+ * get_props:		'addr' is the h/w sc
+ *
+ * INPUTS:		None.
+ *
+ * OUTPUTS:		properties passed up to the audio(4) driver.
+ */
+
+static int
+pwmaudio_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 pwmaudio_trigger_output(void *addr, void *start, void *end, int blksize,
+				    void (*intp)(void *), void *arg, const audio_params_t *aparams)
+{
+
+	struct pwmaudio_softc *sc = addr;
+
+	if (start >= end) {
+		return EINVAL;
+	}
+
+	if (sc->sc_pintr != intp ||
+	    sc->sc_parg != arg) {
+		sc->sc_pintr = intp;
+		sc->sc_parg = arg;
+
+		/* Register the soft interrupt. */
+
+		if (sc->sc_softintcookie != NULL) { 
+			softint_disestablish(sc->sc_softintcookie);
+			sc->sc_softintcookie = NULL;
+		}
+		/* We use the highest priority softint */
+		sc->sc_softintcookie = softint_establish(SOFTINT_CLOCK | SOFTINT_MPSAFE, intp, arg);
+	}
+
+	/* Update sc info. */
+	sc->sc_samplingrate = pwmaudio_sampling_rate;
+	sc->blksize = blksize;
+	sc->blknow = sc->blkstart = start;
+	sc->blkend = end;
+
+	/* Crank up the timer clock interrupt rate. */
+	initrtclock(TIMER_FREQ);
+
+	/* Flag interrupt handler */
+	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 pwmaudio_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/pwmaudiovar.h
===================================================================
RCS file: sys/dev/isa/pwmaudiovar.h
diff -N sys/dev/isa/pwmaudiovar.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ sys/dev/isa/pwmaudiovar.h	15 Jan 2009 00:20:01 -0000
@@ -0,0 +1,68 @@
+/* 	$NetBSD$	 */
+
+/*
+ * pwmaudiovar.h: definitions specific to the pwmaudio driver.
+ */
+
+#ifndef _DEV_ISA_PWMAUDIOVAR_H_
+#define _DEV_ISA_PWMAUDIOVAR_H_
+
+#ifndef NSPKR
+#include "spkr.h"
+#endif /* NSPKR  */
+/* The pwmaudio driver is exclusive to the spkr tone driver: "spkr at pcppi" */
+#if NSPKR > 0
+#error spkr at pcppi, and pwmaudio at pcppi are mutually exclusive. Please check your config file.
+#else
+
+#include <dev/auconv.h>
+#include <dev/audiovar.h>
+#include <sys/timetc.h>
+
+#define PWM_MIXER_DELTA 16
+
+struct pwmaudio_softc {
+	/* audio(9) softc. */
+	struct audio_softc audio_sc;
+  
+	struct pcppi_softc *sc_ppi;
+
+	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 PWMAUDIO_NFORMATS 1
+
+	/* auconv encoding related */
+	struct audio_format sc_audio_formats[PWMAUDIO_NFORMATS];
+	struct audio_encoding_set *sc_encodings;
+
+	struct sysctllog *sc_log; /* sysctl related */
+	int sc_sysctlnode;
+
+};
+
+int	pwmaudio_match(device_t, cfdata_t, void *);
+void	pwmaudio_attach(device_t, device_t, void *);
+int	pwmaudio_detach(device_t, int);
+
+int	pwmaudio_pcppi_attach(device_t);
+
+
+#endif /* NSPKR */
+#endif /* _DEV_ISA_PWMAUDIOVAR_H_ */