xref: /netbsd/sys/dev/spkr.c (revision 6bf7d55e)
1*6bf7d55eSriastradh /*	$NetBSD: spkr.c,v 1.25 2023/03/31 15:00:26 riastradh Exp $	*/
262b1b0deSchristos 
362b1b0deSchristos /*
462b1b0deSchristos  * Copyright (c) 1990 Eric S. Raymond (esr@snark.thyrsus.com)
562b1b0deSchristos  * Copyright (c) 1990 Andrew A. Chernov (ache@astral.msk.su)
662b1b0deSchristos  * Copyright (c) 1990 Lennart Augustsson (lennart@augustsson.net)
762b1b0deSchristos  * All rights reserved.
862b1b0deSchristos  *
962b1b0deSchristos  * Redistribution and use in source and binary forms, with or without
1062b1b0deSchristos  * modification, are permitted provided that the following conditions
1162b1b0deSchristos  * are met:
1262b1b0deSchristos  * 1. Redistributions of source code must retain the above copyright
1362b1b0deSchristos  *    notice, this list of conditions and the following disclaimer.
1462b1b0deSchristos  * 2. Redistributions in binary form must reproduce the above copyright
1562b1b0deSchristos  *    notice, this list of conditions and the following disclaimer in the
1662b1b0deSchristos  *    documentation and/or other materials provided with the distribution.
1762b1b0deSchristos  * 3. All advertising materials mentioning features or use of this software
1862b1b0deSchristos  *    must display the following acknowledgement:
1962b1b0deSchristos  *	This product includes software developed by Eric S. Raymond
2062b1b0deSchristos  * 4. The name of the author may not be used to endorse or promote products
2162b1b0deSchristos  *    derived from this software without specific prior written permission.
2262b1b0deSchristos  *
2362b1b0deSchristos  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
2462b1b0deSchristos  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
2562b1b0deSchristos  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2662b1b0deSchristos  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
2762b1b0deSchristos  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
2862b1b0deSchristos  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2962b1b0deSchristos  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3062b1b0deSchristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
3162b1b0deSchristos  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
3262b1b0deSchristos  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
3362b1b0deSchristos  * POSSIBILITY OF SUCH DAMAGE.
3462b1b0deSchristos  */
3562b1b0deSchristos 
3662b1b0deSchristos /*
3762b1b0deSchristos  * spkr.c -- device driver for console speaker on 80386
3862b1b0deSchristos  *
3962b1b0deSchristos  * v1.1 by Eric S. Raymond (esr@snark.thyrsus.com) Feb 1990
4062b1b0deSchristos  *      modified for 386bsd by Andrew A. Chernov <ache@astral.msk.su>
4162b1b0deSchristos  *      386bsd only clean version, all SYSV stuff removed
4262b1b0deSchristos  *      use hz value from param.c
4362b1b0deSchristos  */
4462b1b0deSchristos 
4562b1b0deSchristos #include <sys/cdefs.h>
46*6bf7d55eSriastradh __KERNEL_RCSID(0, "$NetBSD: spkr.c,v 1.25 2023/03/31 15:00:26 riastradh Exp $");
47e2f2d55bSnat 
48ff054bdaSpgoyette #if defined(_KERNEL_OPT)
49e2f2d55bSnat #include "wsmux.h"
50ff054bdaSpgoyette #endif
5162b1b0deSchristos 
5262b1b0deSchristos #include <sys/param.h>
5362b1b0deSchristos #include <sys/systm.h>
5462b1b0deSchristos #include <sys/kernel.h>
5562b1b0deSchristos #include <sys/errno.h>
5662b1b0deSchristos #include <sys/device.h>
572a59bafeSthorpej #include <sys/kmem.h>
5862b1b0deSchristos #include <sys/module.h>
5962b1b0deSchristos #include <sys/uio.h>
6062b1b0deSchristos #include <sys/proc.h>
6162b1b0deSchristos #include <sys/ioctl.h>
6262b1b0deSchristos #include <sys/conf.h>
6362b1b0deSchristos 
6462b1b0deSchristos #include <sys/bus.h>
6562b1b0deSchristos 
6662b1b0deSchristos #include <dev/spkrio.h>
67a4cc4a33Schristos #include <dev/spkrvar.h>
68e2f2d55bSnat #include <dev/wscons/wsconsio.h>
69e2f2d55bSnat #include <dev/wscons/wsbellvar.h>
70e2f2d55bSnat #include <dev/wscons/wsbellmuxvar.h>
7162b1b0deSchristos 
7214274eaeSriastradh #include "ioconf.h"
7314274eaeSriastradh 
7462b1b0deSchristos dev_type_open(spkropen);
7562b1b0deSchristos dev_type_close(spkrclose);
7662b1b0deSchristos dev_type_write(spkrwrite);
7762b1b0deSchristos dev_type_ioctl(spkrioctl);
7862b1b0deSchristos 
7962b1b0deSchristos const struct cdevsw spkr_cdevsw = {
8062b1b0deSchristos 	.d_open = spkropen,
8162b1b0deSchristos 	.d_close = spkrclose,
8262b1b0deSchristos 	.d_read = noread,
8362b1b0deSchristos 	.d_write = spkrwrite,
8462b1b0deSchristos 	.d_ioctl = spkrioctl,
8562b1b0deSchristos 	.d_stop = nostop,
8662b1b0deSchristos 	.d_tty = notty,
8762b1b0deSchristos 	.d_poll = nopoll,
8862b1b0deSchristos 	.d_mmap = nommap,
8962b1b0deSchristos 	.d_kqfilter = nokqfilter,
9062b1b0deSchristos 	.d_discard = nodiscard,
9162b1b0deSchristos 	.d_flag = D_OTHER
9262b1b0deSchristos };
9362b1b0deSchristos 
94dd2f4acbSchristos static void playinit(struct spkr_softc *);
95dd2f4acbSchristos static void playtone(struct spkr_softc *, int, int, int);
96dd2f4acbSchristos static void playstring(struct spkr_softc *, const char *, size_t);
9762b1b0deSchristos 
9862b1b0deSchristos /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
9962b1b0deSchristos  *
10062b1b0deSchristos  * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
10162b1b0deSchristos  * M[LNS] are missing and the ~ synonym and octave-tracking facility is added.
1027a449eafSisaki  * String play is not interruptible except possibly at physical block
1037a449eafSisaki  * boundaries.
10462b1b0deSchristos  */
10562b1b0deSchristos 
10662b1b0deSchristos /*
10762b1b0deSchristos  * Magic number avoidance...
10862b1b0deSchristos  */
10962b1b0deSchristos #define SECS_PER_MIN	60	/* seconds per minute */
11062b1b0deSchristos #define WHOLE_NOTE	4	/* quarter notes per whole note */
11162b1b0deSchristos #define MIN_VALUE	64	/* the most we can divide a note by */
11262b1b0deSchristos #define DFLT_VALUE	4	/* default value (quarter-note) */
11362b1b0deSchristos #define FILLTIME	8	/* for articulation, break note in parts */
11462b1b0deSchristos #define STACCATO	6	/* 6/8 = 3/4 of note is filled */
11562b1b0deSchristos #define NORMAL		7	/* 7/8ths of note interval is filled */
11662b1b0deSchristos #define LEGATO		8	/* all of note interval is filled */
11762b1b0deSchristos #define DFLT_OCTAVE	4	/* default octave */
11862b1b0deSchristos #define MIN_TEMPO	32	/* minimum tempo */
11962b1b0deSchristos #define DFLT_TEMPO	120	/* default tempo */
12062b1b0deSchristos #define MAX_TEMPO	255	/* max tempo */
12162b1b0deSchristos #define NUM_MULT	3	/* numerator of dot multiplier */
12262b1b0deSchristos #define DENOM_MULT	2	/* denominator of dot multiplier */
12362b1b0deSchristos 
12462b1b0deSchristos /* letter to half-tone:         A   B  C  D  E  F  G */
12562b1b0deSchristos static const int notetab[8] = { 9, 11, 0, 2, 4, 5, 7 };
12662b1b0deSchristos 
12762b1b0deSchristos /*
12862b1b0deSchristos  * This is the American Standard A440 Equal-Tempered scale with frequencies
12962b1b0deSchristos  * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
13062b1b0deSchristos  * our octave 0 is standard octave 2.
13162b1b0deSchristos  */
13262b1b0deSchristos #define OCTAVE_NOTES	12	/* semitones per octave */
13362b1b0deSchristos static const int pitchtab[] =
13462b1b0deSchristos {
13562b1b0deSchristos /*        C     C#    D     D#    E     F     F#    G     G#    A     A#    B*/
13662b1b0deSchristos /* 0 */   65,   69,   73,   78,   82,   87,   93,   98,  103,  110,  117,  123,
13762b1b0deSchristos /* 1 */  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,
13862b1b0deSchristos /* 2 */  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,
13962b1b0deSchristos /* 3 */  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,
14062b1b0deSchristos /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
14162b1b0deSchristos /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
14262b1b0deSchristos /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
14362b1b0deSchristos };
14462b1b0deSchristos #define NOCTAVES (int)(__arraycount(pitchtab) / OCTAVE_NOTES)
14562b1b0deSchristos 
14662b1b0deSchristos static void
playinit(struct spkr_softc * sc)147dd2f4acbSchristos playinit(struct spkr_softc *sc)
14862b1b0deSchristos {
149dd2f4acbSchristos 	sc->sc_octave = DFLT_OCTAVE;
150dd2f4acbSchristos 	sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
151dd2f4acbSchristos 	sc->sc_fill = NORMAL;
152dd2f4acbSchristos 	sc->sc_value = DFLT_VALUE;
153dd2f4acbSchristos 	sc->sc_octtrack = false;
154dd2f4acbSchristos 	sc->sc_octprefix = true;/* act as though there was an initial O(n) */
15562b1b0deSchristos }
15662b1b0deSchristos 
1577a449eafSisaki #define SPKRPRI (PZERO - 1)
15862b1b0deSchristos 
1597a449eafSisaki /* Rest for given number of ticks */
1607a449eafSisaki static void
rest(struct spkr_softc * sc,int ticks)1617a449eafSisaki rest(struct spkr_softc *sc, int ticks)
1627a449eafSisaki {
1637a449eafSisaki 
1647a449eafSisaki #ifdef SPKRDEBUG
1657a449eafSisaki 	device_printf(sc->sc_dev, "%s: rest for %d ticks\n", __func__, ticks);
1667a449eafSisaki #endif /* SPKRDEBUG */
1677a449eafSisaki 	KASSERT(ticks > 0);
1687a449eafSisaki 
1697a449eafSisaki 	tsleep(sc->sc_dev, SPKRPRI | PCATCH, device_xname(sc->sc_dev), ticks);
17062b1b0deSchristos }
17162b1b0deSchristos 
1727a449eafSisaki /*
1737a449eafSisaki  * Play tone of proper duration for current rhythm signature.
1747a449eafSisaki  * note indicates "O0C" = 0, "O0C#" = 1, "O0D" = 2, ... , and
1757a449eafSisaki  * -1 indiacates a rest.
1767a449eafSisaki  * val indicates the length, "L4" = 4, "L8" = 8.
1777a449eafSisaki  * sustain indicates the number of subsequent dots that extend the sound
1787a449eafSisaki  * by one a half.
1797a449eafSisaki  */
1807a449eafSisaki static void
playtone(struct spkr_softc * sc,int note,int val,int sustain)1817a449eafSisaki playtone(struct spkr_softc *sc, int note, int val, int sustain)
1827a449eafSisaki {
1837a449eafSisaki 	int whole;
1847a449eafSisaki 	int total;
1857a449eafSisaki 	int sound;
1867a449eafSisaki 	int silence;
1877a449eafSisaki 
1887a449eafSisaki 	/* this weirdness avoids floating-point arithmetic */
1897a449eafSisaki 	whole = sc->sc_whole;
1907a449eafSisaki 	for (; sustain; sustain--) {
1917a449eafSisaki 		whole *= NUM_MULT;
1927a449eafSisaki 		val *= DENOM_MULT;
1937a449eafSisaki 	}
1947a449eafSisaki 
1957a449eafSisaki 	/* Total length in tick */
1967a449eafSisaki 	total = whole / val;
1977a449eafSisaki 
1987a449eafSisaki 	if (note == -1) {
1997a449eafSisaki #ifdef SPKRDEBUG
2007a449eafSisaki 		device_printf(sc->sc_dev, "%s: rest for %d ticks\n",
2017a449eafSisaki 		    __func__, total);
2027a449eafSisaki #endif /* SPKRDEBUG */
2037a449eafSisaki 		if (total != 0)
2047a449eafSisaki 			rest(sc, total);
205dd2f4acbSchristos 		return;
206dd2f4acbSchristos 	}
207*6bf7d55eSriastradh 	KASSERTMSG(note < __arraycount(pitchtab), "note=%d", note);
208dd2f4acbSchristos 
2097a449eafSisaki 	/*
2107a449eafSisaki 	 * Rest 1/8 (if NORMAL) or 3/8 (if STACCATO) in tick.
2117a449eafSisaki 	 * silence should be rounded down.
2127a449eafSisaki 	 */
2137a449eafSisaki 	silence = total * (FILLTIME - sc->sc_fill) / FILLTIME;
2147a449eafSisaki 	sound = total - silence;
21562b1b0deSchristos 
21662b1b0deSchristos #ifdef SPKRDEBUG
217c195ab7cSisaki 	device_printf(sc->sc_dev,
2187a449eafSisaki 	    "%s: note %d for %d ticks, rest for %d ticks\n", __func__,
2197a449eafSisaki 	    note, sound, silence);
22062b1b0deSchristos #endif /* SPKRDEBUG */
22162b1b0deSchristos 
2227a449eafSisaki 	if (sound != 0)
2237a449eafSisaki 		(*sc->sc_tone)(sc->sc_dev, pitchtab[note], sound);
2247a449eafSisaki 	if (silence != 0)
2257a449eafSisaki 		rest(sc, silence);
22662b1b0deSchristos }
22762b1b0deSchristos 
22862b1b0deSchristos /* interpret and play an item from a notation string */
229dd2f4acbSchristos static void
playstring(struct spkr_softc * sc,const char * cp,size_t slen)230dd2f4acbSchristos playstring(struct spkr_softc *sc, const char *cp, size_t slen)
23162b1b0deSchristos {
2327a449eafSisaki 	int pitch;
2337a449eafSisaki 	int lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
23462b1b0deSchristos 
235dd2f4acbSchristos #define GETNUM(cp, v)	\
236dd2f4acbSchristos 	for (v = 0; slen > 0 && isdigit((unsigned char)cp[1]); ) { \
237*6bf7d55eSriastradh 		if (v > INT_MAX/10 - (cp[1] - '0')) { \
238*6bf7d55eSriastradh 			v = INT_MAX; \
239*6bf7d55eSriastradh 			continue; \
240*6bf7d55eSriastradh 		} \
241dd2f4acbSchristos 		v = v * 10 + (*++cp - '0'); \
242dd2f4acbSchristos 		slen--; \
243dd2f4acbSchristos 	}
244dd2f4acbSchristos 
245dd2f4acbSchristos 	for (; slen--; cp++) {
24662b1b0deSchristos 		int sustain, timeval, tempo;
247dd2f4acbSchristos 		char c = toupper((unsigned char)*cp);
24862b1b0deSchristos 
24962b1b0deSchristos #ifdef SPKRDEBUG
250c195ab7cSisaki 		if (0x20 <= c && c < 0x7f) {
251c195ab7cSisaki 			device_printf(sc->sc_dev, "%s: '%c'\n", __func__, c);
252c195ab7cSisaki 		} else {
253c195ab7cSisaki 			device_printf(sc->sc_dev, "%s: (0x%x)\n", __func__, c);
254c195ab7cSisaki 		}
25562b1b0deSchristos #endif /* SPKRDEBUG */
25662b1b0deSchristos 
257dd2f4acbSchristos 		switch (c) {
258dd2f4acbSchristos 		case 'A': case 'B': case 'C': case 'D':
259dd2f4acbSchristos 		case 'E': case 'F': case 'G':
26062b1b0deSchristos 			/* compute pitch */
261dd2f4acbSchristos 			pitch = notetab[c - 'A'] + sc->sc_octave * OCTAVE_NOTES;
26262b1b0deSchristos 
26362b1b0deSchristos 			/* this may be followed by an accidental sign */
264dd2f4acbSchristos 			if (slen > 0 && (cp[1] == '#' || cp[1] == '+')) {
26562b1b0deSchristos 				++pitch;
26662b1b0deSchristos 				++cp;
26762b1b0deSchristos 				slen--;
268dd2f4acbSchristos 			} else if (slen > 0 && cp[1] == '-') {
26962b1b0deSchristos 				--pitch;
27062b1b0deSchristos 				++cp;
27162b1b0deSchristos 				slen--;
27262b1b0deSchristos 			}
27362b1b0deSchristos 
27462b1b0deSchristos 			/*
275dd2f4acbSchristos 			 * If octave-tracking mode is on, and there has been no
276dd2f4acbSchristos 			 * octave- setting prefix, find the version of the
277dd2f4acbSchristos 			 * current letter note * closest to the last
278dd2f4acbSchristos 			 * regardless of octave.
27962b1b0deSchristos 			 */
280dd2f4acbSchristos 			if (sc->sc_octtrack && !sc->sc_octprefix) {
281dd2f4acbSchristos 				int d = abs(pitch - lastpitch);
282dd2f4acbSchristos 				if (d > abs(pitch + OCTAVE_NOTES - lastpitch)) {
283dd2f4acbSchristos 					if (sc->sc_octave < NOCTAVES - 1) {
284dd2f4acbSchristos 						++sc->sc_octave;
28562b1b0deSchristos 						pitch += OCTAVE_NOTES;
28662b1b0deSchristos 					}
28762b1b0deSchristos 				}
28862b1b0deSchristos 
289dd2f4acbSchristos 				if (d > abs(pitch - OCTAVE_NOTES - lastpitch)) {
290dd2f4acbSchristos 					if (sc->sc_octave > 0) {
291dd2f4acbSchristos 						--sc->sc_octave;
29262b1b0deSchristos 						pitch -= OCTAVE_NOTES;
29362b1b0deSchristos 					}
29462b1b0deSchristos 				}
29562b1b0deSchristos 			}
296dd2f4acbSchristos 			sc->sc_octprefix = false;
29762b1b0deSchristos 			lastpitch = pitch;
29862b1b0deSchristos 
299dd2f4acbSchristos 			/*
300dd2f4acbSchristos 			 * ...which may in turn be followed by an override
301dd2f4acbSchristos 			 * time value
302dd2f4acbSchristos 			 */
30362b1b0deSchristos 			GETNUM(cp, timeval);
30462b1b0deSchristos 			if (timeval <= 0 || timeval > MIN_VALUE)
305dd2f4acbSchristos 				timeval = sc->sc_value;
30662b1b0deSchristos 
30762b1b0deSchristos 			/* ...and/or sustain dots */
308dd2f4acbSchristos 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
30962b1b0deSchristos 				slen--;
31062b1b0deSchristos 				sustain++;
31162b1b0deSchristos 			}
31262b1b0deSchristos 
31362b1b0deSchristos 			/* time to emit the actual tone */
314dd2f4acbSchristos 			playtone(sc, pitch, timeval, sustain);
31562b1b0deSchristos 			break;
31662b1b0deSchristos 
31762b1b0deSchristos 		case 'O':
318dd2f4acbSchristos 			if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
319dd2f4acbSchristos 				sc->sc_octprefix = sc->sc_octtrack = false;
32062b1b0deSchristos 				++cp;
32162b1b0deSchristos 				slen--;
322dd2f4acbSchristos 			} else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
323dd2f4acbSchristos 				sc->sc_octtrack = true;
32462b1b0deSchristos 				++cp;
32562b1b0deSchristos 				slen--;
326dd2f4acbSchristos 			} else {
327dd2f4acbSchristos 				GETNUM(cp, sc->sc_octave);
328*6bf7d55eSriastradh 				KASSERTMSG(sc->sc_octave >= 0, "%d",
329*6bf7d55eSriastradh 				    sc->sc_octave);
330dd2f4acbSchristos 				if (sc->sc_octave >= NOCTAVES)
331dd2f4acbSchristos 					sc->sc_octave = DFLT_OCTAVE;
332dd2f4acbSchristos 				sc->sc_octprefix = true;
33362b1b0deSchristos 			}
33462b1b0deSchristos 			break;
33562b1b0deSchristos 
33662b1b0deSchristos 		case '>':
337dd2f4acbSchristos 			if (sc->sc_octave < NOCTAVES - 1)
338dd2f4acbSchristos 				sc->sc_octave++;
339dd2f4acbSchristos 			sc->sc_octprefix = true;
34062b1b0deSchristos 			break;
34162b1b0deSchristos 
34262b1b0deSchristos 		case '<':
343dd2f4acbSchristos 			if (sc->sc_octave > 0)
344dd2f4acbSchristos 				sc->sc_octave--;
345dd2f4acbSchristos 			sc->sc_octprefix = true;
34662b1b0deSchristos 			break;
34762b1b0deSchristos 
34862b1b0deSchristos 		case 'N':
34962b1b0deSchristos 			GETNUM(cp, pitch);
350*6bf7d55eSriastradh 			KASSERTMSG(pitch >= 0, "pitch=%d", pitch);
351*6bf7d55eSriastradh 			if (pitch >= __arraycount(pitchtab))
352*6bf7d55eSriastradh 				break;
353dd2f4acbSchristos 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
35462b1b0deSchristos 				slen--;
35562b1b0deSchristos 				sustain++;
35662b1b0deSchristos 			}
357dd2f4acbSchristos 			playtone(sc, pitch - 1, sc->sc_value, sustain);
35862b1b0deSchristos 			break;
35962b1b0deSchristos 
36062b1b0deSchristos 		case 'L':
361dd2f4acbSchristos 			GETNUM(cp, sc->sc_value);
362dd2f4acbSchristos 			if (sc->sc_value <= 0 || sc->sc_value > MIN_VALUE)
363dd2f4acbSchristos 				sc->sc_value = DFLT_VALUE;
36462b1b0deSchristos 			break;
36562b1b0deSchristos 
36662b1b0deSchristos 		case 'P':
36762b1b0deSchristos 		case '~':
36862b1b0deSchristos 			/* this may be followed by an override time value */
36962b1b0deSchristos 			GETNUM(cp, timeval);
37062b1b0deSchristos 			if (timeval <= 0 || timeval > MIN_VALUE)
371dd2f4acbSchristos 				timeval = sc->sc_value;
372dd2f4acbSchristos 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
37362b1b0deSchristos 				slen--;
37462b1b0deSchristos 				sustain++;
37562b1b0deSchristos 			}
376dd2f4acbSchristos 			playtone(sc, -1, timeval, sustain);
37762b1b0deSchristos 			break;
37862b1b0deSchristos 
37962b1b0deSchristos 		case 'T':
38062b1b0deSchristos 			GETNUM(cp, tempo);
38162b1b0deSchristos 			if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
38262b1b0deSchristos 				tempo = DFLT_TEMPO;
383dd2f4acbSchristos 			sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
38462b1b0deSchristos 			break;
38562b1b0deSchristos 
38662b1b0deSchristos 		case 'M':
387dd2f4acbSchristos 			if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
388dd2f4acbSchristos 				sc->sc_fill = NORMAL;
38962b1b0deSchristos 				++cp;
39062b1b0deSchristos 				slen--;
391dd2f4acbSchristos 			} else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
392dd2f4acbSchristos 				sc->sc_fill = LEGATO;
39362b1b0deSchristos 				++cp;
39462b1b0deSchristos 				slen--;
395dd2f4acbSchristos 			} else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's')) {
396dd2f4acbSchristos 				sc->sc_fill = STACCATO;
39762b1b0deSchristos 				++cp;
39862b1b0deSchristos 				slen--;
39962b1b0deSchristos 			}
40062b1b0deSchristos 			break;
40162b1b0deSchristos 		}
40262b1b0deSchristos 	}
40362b1b0deSchristos }
40462b1b0deSchristos 
4057a449eafSisaki /******************* UNIX DRIVER HOOKS BEGIN HERE **************************/
406dd2f4acbSchristos #define spkrenter(d)	device_lookup_private(&spkr_cd, d)
40762b1b0deSchristos 
4087a449eafSisaki /*
4097a449eafSisaki  * Attaches spkr.  Specify tone function with the following specification:
4107a449eafSisaki  *
4117a449eafSisaki  * void
4127a449eafSisaki  * tone(device_t self, u_int pitch, u_int tick)
4137a449eafSisaki  *	plays a beep with specified parameters.
4147a449eafSisaki  *	The argument 'pitch' specifies the pitch of a beep in Hz.  The argument
4157a449eafSisaki  *	'tick' specifies the period of a beep in tick(9).  This function waits
4167a449eafSisaki  *	to finish playing the beep and then halts it.
4177a449eafSisaki  *	If the pitch is zero, it halts all sound if any (for compatibility
4187a449eafSisaki  *	with the past confused specifications, but there should be no sound at
4197a449eafSisaki  *	this point).  And it returns immediately, without waiting ticks.  So
4207a449eafSisaki  *	you cannot use this as a rest.
4217a449eafSisaki  *	If the tick is zero, it returns immediately.
4227a449eafSisaki  */
423dd2f4acbSchristos void
spkr_attach(device_t self,void (* tone)(device_t,u_int,u_int))4247a449eafSisaki spkr_attach(device_t self, void (*tone)(device_t, u_int, u_int))
42562b1b0deSchristos {
426dd2f4acbSchristos 	struct spkr_softc *sc = device_private(self);
427dd2f4acbSchristos 
428bccce848Spgoyette #ifdef SPKRDEBUG
429e8b15cb7Sriastradh 	aprint_debug("%s: entering for unit %d\n", __func__,
430e8b15cb7Sriastradh 	    device_unit(self));
431bccce848Spgoyette #endif /* SPKRDEBUG */
432dd2f4acbSchristos 	sc->sc_dev = self;
433dd2f4acbSchristos 	sc->sc_tone = tone;
434dd2f4acbSchristos 	sc->sc_inbuf = NULL;
43525c4f25dSpgoyette 	sc->sc_wsbelldev = NULL;
436e2f2d55bSnat 
4373bee0c11Sthorpej 	spkr_rescan(self, NULL, NULL);
43862b1b0deSchristos }
43962b1b0deSchristos 
44062b1b0deSchristos int
spkr_detach(device_t self,int flags)441bccce848Spgoyette spkr_detach(device_t self, int flags)
442bccce848Spgoyette {
443bccce848Spgoyette 	struct spkr_softc *sc = device_private(self);
4443e5646a1Snat 	int rc;
445bccce848Spgoyette 
446bccce848Spgoyette #ifdef SPKRDEBUG
447e8b15cb7Sriastradh 	aprint_debug("%s: entering for unit %d\n", __func__,
448e8b15cb7Sriastradh 	    device_unit(self));
449bccce848Spgoyette #endif /* SPKRDEBUG */
450bccce848Spgoyette 	if (sc == NULL)
451bccce848Spgoyette 		return ENXIO;
452a642e421Snat 
453a642e421Snat 	/* XXXNS If speaker never closes, we cannot complete the detach. */
454a642e421Snat 	while ((flags & DETACH_FORCE) != 0 && sc->sc_inbuf != NULL)
455a642e421Snat 		kpause("spkrwait", TRUE, 1, NULL);
456bccce848Spgoyette 	if (sc->sc_inbuf != NULL)
457bccce848Spgoyette 		return EBUSY;
458bccce848Spgoyette 
4593e5646a1Snat 	rc = config_detach_children(self, flags);
4603e5646a1Snat 
4613e5646a1Snat 	return rc;
462bccce848Spgoyette }
463bccce848Spgoyette 
46425c4f25dSpgoyette /* ARGSUSED */
46525c4f25dSpgoyette int
spkr_rescan(device_t self,const char * iattr,const int * locators)46625c4f25dSpgoyette spkr_rescan(device_t self, const char *iattr, const int *locators)
46725c4f25dSpgoyette {
46825c4f25dSpgoyette #if NWSMUX > 0
46925c4f25dSpgoyette 	struct spkr_softc *sc = device_private(self);
47025c4f25dSpgoyette 	struct wsbelldev_attach_args a;
47125c4f25dSpgoyette 
47225c4f25dSpgoyette 	if (sc->sc_wsbelldev == NULL) {
47325c4f25dSpgoyette 		a.accesscookie = sc;
4743bee0c11Sthorpej 		sc->sc_wsbelldev = config_found(self, &a, wsbelldevprint,
475beecddb6Sthorpej 		    CFARGS_NONE);
47625c4f25dSpgoyette 	}
47725c4f25dSpgoyette #endif
47825c4f25dSpgoyette 	return 0;
47925c4f25dSpgoyette }
48025c4f25dSpgoyette 
48125c4f25dSpgoyette int
spkr_childdet(device_t self,device_t child)48225c4f25dSpgoyette spkr_childdet(device_t self, device_t child)
48325c4f25dSpgoyette {
48425c4f25dSpgoyette 	struct spkr_softc *sc = device_private(self);
48525c4f25dSpgoyette 
48625c4f25dSpgoyette 	if (sc->sc_wsbelldev == child)
48725c4f25dSpgoyette 		sc->sc_wsbelldev = NULL;
48825c4f25dSpgoyette 
48925c4f25dSpgoyette 	return 0;
49025c4f25dSpgoyette }
49125c4f25dSpgoyette 
492bccce848Spgoyette int
spkropen(dev_t dev,int flags,int mode,struct lwp * l)49362b1b0deSchristos spkropen(dev_t dev, int	flags, int mode, struct lwp *l)
49462b1b0deSchristos {
495dd2f4acbSchristos 	struct spkr_softc *sc = spkrenter(minor(dev));
49662b1b0deSchristos 
497c195ab7cSisaki #ifdef SPKRDEBUG
498c195ab7cSisaki 	device_printf(sc->sc_dev, "%s: entering\n", __func__);
499c195ab7cSisaki #endif /* SPKRDEBUG */
500dd2f4acbSchristos 	if (sc == NULL)
501dd2f4acbSchristos 		return ENXIO;
502bccce848Spgoyette 	if (sc->sc_inbuf != NULL)
503dd2f4acbSchristos 		return EBUSY;
504dd2f4acbSchristos 
5052a59bafeSthorpej 	sc->sc_inbuf = kmem_alloc(DEV_BSIZE, KM_SLEEP);
506dd2f4acbSchristos 	playinit(sc);
507dd2f4acbSchristos 	return 0;
50862b1b0deSchristos }
50962b1b0deSchristos 
51062b1b0deSchristos int
spkrwrite(dev_t dev,struct uio * uio,int flags)51162b1b0deSchristos spkrwrite(dev_t dev, struct uio *uio, int flags)
51262b1b0deSchristos {
513dd2f4acbSchristos 	struct spkr_softc *sc = spkrenter(minor(dev));
51462b1b0deSchristos 
515c195ab7cSisaki #ifdef SPKRDEBUG
516c195ab7cSisaki 	device_printf(sc->sc_dev, "%s: entering with length = %zu\n",
517c195ab7cSisaki 	    __func__, uio->uio_resid);
518c195ab7cSisaki #endif /* SPKRDEBUG */
519dd2f4acbSchristos 	if (sc == NULL)
520dd2f4acbSchristos 		return ENXIO;
521bccce848Spgoyette 	if (sc->sc_inbuf == NULL)
522dd2f4acbSchristos 		return EINVAL;
523dd2f4acbSchristos 
524a8a5c538Sriastradh 	size_t n = uimin(DEV_BSIZE, uio->uio_resid);
525dd2f4acbSchristos 	int error = uiomove(sc->sc_inbuf, n, uio);
526dd2f4acbSchristos 	if (error)
527dd2f4acbSchristos 		return error;
528dd2f4acbSchristos 	playstring(sc, sc->sc_inbuf, n);
529dd2f4acbSchristos 	return 0;
53062b1b0deSchristos }
53162b1b0deSchristos 
53262b1b0deSchristos int
spkrclose(dev_t dev,int flags,int mode,struct lwp * l)53362b1b0deSchristos spkrclose(dev_t dev, int flags, int mode, struct lwp *l)
53462b1b0deSchristos {
535dd2f4acbSchristos 	struct spkr_softc *sc = spkrenter(minor(dev));
53662b1b0deSchristos 
537c195ab7cSisaki #ifdef SPKRDEBUG
538c195ab7cSisaki 	device_printf(sc->sc_dev, "%s: entering\n", __func__);
539c195ab7cSisaki #endif /* SPKRDEBUG */
540dd2f4acbSchristos 	if (sc == NULL)
541dd2f4acbSchristos 		return ENXIO;
542bccce848Spgoyette 	if (sc->sc_inbuf == NULL)
543dd2f4acbSchristos 		return EINVAL;
544dd2f4acbSchristos 
545dd2f4acbSchristos 	sc->sc_tone(sc->sc_dev, 0, 0);
5462a59bafeSthorpej 	kmem_free(sc->sc_inbuf, DEV_BSIZE);
547dd2f4acbSchristos 	sc->sc_inbuf = NULL;
548dd2f4acbSchristos 
549dd2f4acbSchristos 	return 0;
55062b1b0deSchristos }
551dd2f4acbSchristos 
5527a449eafSisaki /*
5537a449eafSisaki  * Play tone specified by tp.
5547a449eafSisaki  * tp->frequency is the frequency (0 means a rest).
5557a449eafSisaki  * tp->duration is the length in tick (returns immediately if 0).
5567a449eafSisaki  */
557dd2f4acbSchristos static void
playonetone(struct spkr_softc * sc,tone_t * tp)558dd2f4acbSchristos playonetone(struct spkr_softc *sc, tone_t *tp)
559dd2f4acbSchristos {
5607a449eafSisaki 	if (tp->duration <= 0)
5617a449eafSisaki 		return;
5627a449eafSisaki 
563dd2f4acbSchristos 	if (tp->frequency == 0)
5647a449eafSisaki 		rest(sc, tp->duration);
565dd2f4acbSchristos 	else
566dd2f4acbSchristos 		(*sc->sc_tone)(sc->sc_dev, tp->frequency, tp->duration);
56762b1b0deSchristos }
56862b1b0deSchristos 
56962b1b0deSchristos int
spkrioctl(dev_t dev,u_long cmd,void * data,int flag,struct lwp * l)57062b1b0deSchristos spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
57162b1b0deSchristos {
572c195ab7cSisaki 	struct spkr_softc *sc = spkrenter(minor(dev));
573dd2f4acbSchristos 	tone_t *tp;
57462b1b0deSchristos 	tone_t ttp;
57562b1b0deSchristos 	int error;
576c195ab7cSisaki 
577dd2f4acbSchristos #ifdef SPKRDEBUG
578c195ab7cSisaki 	device_printf(sc->sc_dev, "%s: entering with cmd = %lx\n",
579c195ab7cSisaki 	    __func__, cmd);
580dd2f4acbSchristos #endif /* SPKRDEBUG */
581dd2f4acbSchristos 	if (sc == NULL)
582dd2f4acbSchristos 		return ENXIO;
583bccce848Spgoyette 	if (sc->sc_inbuf == NULL)
584dd2f4acbSchristos 		return EINVAL;
585dd2f4acbSchristos 
586dd2f4acbSchristos 	switch (cmd) {
587dd2f4acbSchristos 	case SPKRTONE:
588dd2f4acbSchristos 		playonetone(sc, data);
589dd2f4acbSchristos 		return 0;
590dd2f4acbSchristos 	case SPKRTUNE:
591dd2f4acbSchristos 		for (tp = *(void **)data;; tp++) {
59262b1b0deSchristos 			error = copyin(tp, &ttp, sizeof(tone_t));
59362b1b0deSchristos 			if (error)
59462b1b0deSchristos 				return(error);
59562b1b0deSchristos 			if (ttp.duration == 0)
59662b1b0deSchristos 				break;
597dd2f4acbSchristos 			playonetone(sc, &ttp);
59862b1b0deSchristos 		}
599dd2f4acbSchristos 		return 0;
600bfcf5f24Snat 	case SPKRGETVOL:
601bfcf5f24Snat 		if (data != NULL)
602bfcf5f24Snat 			*(u_int *)data = sc->sc_vol;
603bfcf5f24Snat 		return 0;
604bfcf5f24Snat 	case SPKRSETVOL:
605bfcf5f24Snat 		if (data != NULL && *(u_int *)data <= 100)
606bfcf5f24Snat 			sc->sc_vol = *(u_int *)data;
607bfcf5f24Snat 		return 0;
608dd2f4acbSchristos 	default:
609dd2f4acbSchristos 		return ENOTTY;
61062b1b0deSchristos 	}
61162b1b0deSchristos }
61262b1b0deSchristos 
61346bee1b9Schristos #ifdef _MODULE
61446bee1b9Schristos #include "ioconf.c"
61546bee1b9Schristos #endif
61646bee1b9Schristos 
617f309403eSpgoyette MODULE(MODULE_CLASS_DRIVER, spkr, "audio" /* and/or pcppi */ );
618261a65d5Spgoyette 
61962b1b0deSchristos int
spkr_modcmd(modcmd_t cmd,void * arg)620261a65d5Spgoyette spkr_modcmd(modcmd_t cmd, void *arg)
62162b1b0deSchristos {
6226391aa4bSpgoyette 	int error = 0;
62362b1b0deSchristos #ifdef _MODULE
62462b1b0deSchristos 	devmajor_t bmajor, cmajor;
6256391aa4bSpgoyette #endif
62662b1b0deSchristos 
62762b1b0deSchristos 	switch(cmd) {
62862b1b0deSchristos 	case MODULE_CMD_INIT:
6296391aa4bSpgoyette #ifdef _MODULE
63062b1b0deSchristos 		bmajor = cmajor = -1;
63162b1b0deSchristos 		error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
63262b1b0deSchristos 		    &spkr_cdevsw, &cmajor);
63362b1b0deSchristos 		if (error)
63462b1b0deSchristos 			break;
63562b1b0deSchristos 
63662b1b0deSchristos 		error = config_init_component(cfdriver_ioconf_spkr,
63762b1b0deSchristos 		    cfattach_ioconf_spkr, cfdata_ioconf_spkr);
63862b1b0deSchristos 		if (error) {
63962b1b0deSchristos 			devsw_detach(NULL, &spkr_cdevsw);
64062b1b0deSchristos 		}
6416391aa4bSpgoyette #endif
64262b1b0deSchristos 		break;
64362b1b0deSchristos 
64462b1b0deSchristos 	case MODULE_CMD_FINI:
6456391aa4bSpgoyette #ifdef _MODULE
64662b1b0deSchristos 		error = config_fini_component(cfdriver_ioconf_spkr,
64762b1b0deSchristos 		    cfattach_ioconf_spkr, cfdata_ioconf_spkr);
64874849b7bSpgoyette 		if (error == 0)
64974849b7bSpgoyette 			devsw_detach(NULL, &spkr_cdevsw);
6506391aa4bSpgoyette #endif
65162b1b0deSchristos 		break;
652bccce848Spgoyette 
65362b1b0deSchristos 	default:
65462b1b0deSchristos 		error = ENOTTY;
65562b1b0deSchristos 		break;
65662b1b0deSchristos 	}
65762b1b0deSchristos 
65862b1b0deSchristos 	return error;
65962b1b0deSchristos }
660