xref: /netbsd/sys/dev/spkr.c (revision e8b15cb7)
1*e8b15cb7Sriastradh /*	$NetBSD: spkr.c,v 1.22 2022/02/12 03:24:36 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*e8b15cb7Sriastradh __KERNEL_RCSID(0, "$NetBSD: spkr.c,v 1.22 2022/02/12 03:24:36 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>
5762b1b0deSchristos #include <sys/malloc.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
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
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
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 	}
207dd2f4acbSchristos 
2087a449eafSisaki 	/*
2097a449eafSisaki 	 * Rest 1/8 (if NORMAL) or 3/8 (if STACCATO) in tick.
2107a449eafSisaki 	 * silence should be rounded down.
2117a449eafSisaki 	 */
2127a449eafSisaki 	silence = total * (FILLTIME - sc->sc_fill) / FILLTIME;
2137a449eafSisaki 	sound = total - silence;
21462b1b0deSchristos 
21562b1b0deSchristos #ifdef SPKRDEBUG
216c195ab7cSisaki 	device_printf(sc->sc_dev,
2177a449eafSisaki 	    "%s: note %d for %d ticks, rest for %d ticks\n", __func__,
2187a449eafSisaki 	    note, sound, silence);
21962b1b0deSchristos #endif /* SPKRDEBUG */
22062b1b0deSchristos 
2217a449eafSisaki 	if (sound != 0)
2227a449eafSisaki 		(*sc->sc_tone)(sc->sc_dev, pitchtab[note], sound);
2237a449eafSisaki 	if (silence != 0)
2247a449eafSisaki 		rest(sc, silence);
22562b1b0deSchristos }
22662b1b0deSchristos 
22762b1b0deSchristos /* interpret and play an item from a notation string */
228dd2f4acbSchristos static void
229dd2f4acbSchristos playstring(struct spkr_softc *sc, const char *cp, size_t slen)
23062b1b0deSchristos {
2317a449eafSisaki 	int pitch;
2327a449eafSisaki 	int lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
23362b1b0deSchristos 
234dd2f4acbSchristos #define GETNUM(cp, v)	\
235dd2f4acbSchristos 	for (v = 0; slen > 0 && isdigit((unsigned char)cp[1]); ) { \
236dd2f4acbSchristos 		v = v * 10 + (*++cp - '0'); \
237dd2f4acbSchristos 		slen--; \
238dd2f4acbSchristos 	}
239dd2f4acbSchristos 
240dd2f4acbSchristos 	for (; slen--; cp++) {
24162b1b0deSchristos 		int sustain, timeval, tempo;
242dd2f4acbSchristos 		char c = toupper((unsigned char)*cp);
24362b1b0deSchristos 
24462b1b0deSchristos #ifdef SPKRDEBUG
245c195ab7cSisaki 		if (0x20 <= c && c < 0x7f) {
246c195ab7cSisaki 			device_printf(sc->sc_dev, "%s: '%c'\n", __func__, c);
247c195ab7cSisaki 		} else {
248c195ab7cSisaki 			device_printf(sc->sc_dev, "%s: (0x%x)\n", __func__, c);
249c195ab7cSisaki 		}
25062b1b0deSchristos #endif /* SPKRDEBUG */
25162b1b0deSchristos 
252dd2f4acbSchristos 		switch (c) {
253dd2f4acbSchristos 		case 'A': case 'B': case 'C': case 'D':
254dd2f4acbSchristos 		case 'E': case 'F': case 'G':
25562b1b0deSchristos 			/* compute pitch */
256dd2f4acbSchristos 			pitch = notetab[c - 'A'] + sc->sc_octave * OCTAVE_NOTES;
25762b1b0deSchristos 
25862b1b0deSchristos 			/* this may be followed by an accidental sign */
259dd2f4acbSchristos 			if (slen > 0 && (cp[1] == '#' || cp[1] == '+')) {
26062b1b0deSchristos 				++pitch;
26162b1b0deSchristos 				++cp;
26262b1b0deSchristos 				slen--;
263dd2f4acbSchristos 			} else if (slen > 0 && cp[1] == '-') {
26462b1b0deSchristos 				--pitch;
26562b1b0deSchristos 				++cp;
26662b1b0deSchristos 				slen--;
26762b1b0deSchristos 			}
26862b1b0deSchristos 
26962b1b0deSchristos 			/*
270dd2f4acbSchristos 			 * If octave-tracking mode is on, and there has been no
271dd2f4acbSchristos 			 * octave- setting prefix, find the version of the
272dd2f4acbSchristos 			 * current letter note * closest to the last
273dd2f4acbSchristos 			 * regardless of octave.
27462b1b0deSchristos 			 */
275dd2f4acbSchristos 			if (sc->sc_octtrack && !sc->sc_octprefix) {
276dd2f4acbSchristos 				int d = abs(pitch - lastpitch);
277dd2f4acbSchristos 				if (d > abs(pitch + OCTAVE_NOTES - lastpitch)) {
278dd2f4acbSchristos 					if (sc->sc_octave < NOCTAVES - 1) {
279dd2f4acbSchristos 						++sc->sc_octave;
28062b1b0deSchristos 						pitch += OCTAVE_NOTES;
28162b1b0deSchristos 					}
28262b1b0deSchristos 				}
28362b1b0deSchristos 
284dd2f4acbSchristos 				if (d > abs(pitch - OCTAVE_NOTES - lastpitch)) {
285dd2f4acbSchristos 					if (sc->sc_octave > 0) {
286dd2f4acbSchristos 						--sc->sc_octave;
28762b1b0deSchristos 						pitch -= OCTAVE_NOTES;
28862b1b0deSchristos 					}
28962b1b0deSchristos 				}
29062b1b0deSchristos 			}
291dd2f4acbSchristos 			sc->sc_octprefix = false;
29262b1b0deSchristos 			lastpitch = pitch;
29362b1b0deSchristos 
294dd2f4acbSchristos 			/*
295dd2f4acbSchristos 			 * ...which may in turn be followed by an override
296dd2f4acbSchristos 			 * time value
297dd2f4acbSchristos 			 */
29862b1b0deSchristos 			GETNUM(cp, timeval);
29962b1b0deSchristos 			if (timeval <= 0 || timeval > MIN_VALUE)
300dd2f4acbSchristos 				timeval = sc->sc_value;
30162b1b0deSchristos 
30262b1b0deSchristos 			/* ...and/or sustain dots */
303dd2f4acbSchristos 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
30462b1b0deSchristos 				slen--;
30562b1b0deSchristos 				sustain++;
30662b1b0deSchristos 			}
30762b1b0deSchristos 
30862b1b0deSchristos 			/* time to emit the actual tone */
309dd2f4acbSchristos 			playtone(sc, pitch, timeval, sustain);
31062b1b0deSchristos 			break;
31162b1b0deSchristos 
31262b1b0deSchristos 		case 'O':
313dd2f4acbSchristos 			if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
314dd2f4acbSchristos 				sc->sc_octprefix = sc->sc_octtrack = false;
31562b1b0deSchristos 				++cp;
31662b1b0deSchristos 				slen--;
317dd2f4acbSchristos 			} else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
318dd2f4acbSchristos 				sc->sc_octtrack = true;
31962b1b0deSchristos 				++cp;
32062b1b0deSchristos 				slen--;
321dd2f4acbSchristos 			} else {
322dd2f4acbSchristos 				GETNUM(cp, sc->sc_octave);
323dd2f4acbSchristos 				if (sc->sc_octave >= NOCTAVES)
324dd2f4acbSchristos 					sc->sc_octave = DFLT_OCTAVE;
325dd2f4acbSchristos 				sc->sc_octprefix = true;
32662b1b0deSchristos 			}
32762b1b0deSchristos 			break;
32862b1b0deSchristos 
32962b1b0deSchristos 		case '>':
330dd2f4acbSchristos 			if (sc->sc_octave < NOCTAVES - 1)
331dd2f4acbSchristos 				sc->sc_octave++;
332dd2f4acbSchristos 			sc->sc_octprefix = true;
33362b1b0deSchristos 			break;
33462b1b0deSchristos 
33562b1b0deSchristos 		case '<':
336dd2f4acbSchristos 			if (sc->sc_octave > 0)
337dd2f4acbSchristos 				sc->sc_octave--;
338dd2f4acbSchristos 			sc->sc_octprefix = true;
33962b1b0deSchristos 			break;
34062b1b0deSchristos 
34162b1b0deSchristos 		case 'N':
34262b1b0deSchristos 			GETNUM(cp, pitch);
343dd2f4acbSchristos 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
34462b1b0deSchristos 				slen--;
34562b1b0deSchristos 				sustain++;
34662b1b0deSchristos 			}
347dd2f4acbSchristos 			playtone(sc, pitch - 1, sc->sc_value, sustain);
34862b1b0deSchristos 			break;
34962b1b0deSchristos 
35062b1b0deSchristos 		case 'L':
351dd2f4acbSchristos 			GETNUM(cp, sc->sc_value);
352dd2f4acbSchristos 			if (sc->sc_value <= 0 || sc->sc_value > MIN_VALUE)
353dd2f4acbSchristos 				sc->sc_value = DFLT_VALUE;
35462b1b0deSchristos 			break;
35562b1b0deSchristos 
35662b1b0deSchristos 		case 'P':
35762b1b0deSchristos 		case '~':
35862b1b0deSchristos 			/* this may be followed by an override time value */
35962b1b0deSchristos 			GETNUM(cp, timeval);
36062b1b0deSchristos 			if (timeval <= 0 || timeval > MIN_VALUE)
361dd2f4acbSchristos 				timeval = sc->sc_value;
362dd2f4acbSchristos 			for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
36362b1b0deSchristos 				slen--;
36462b1b0deSchristos 				sustain++;
36562b1b0deSchristos 			}
366dd2f4acbSchristos 			playtone(sc, -1, timeval, sustain);
36762b1b0deSchristos 			break;
36862b1b0deSchristos 
36962b1b0deSchristos 		case 'T':
37062b1b0deSchristos 			GETNUM(cp, tempo);
37162b1b0deSchristos 			if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
37262b1b0deSchristos 				tempo = DFLT_TEMPO;
373dd2f4acbSchristos 			sc->sc_whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
37462b1b0deSchristos 			break;
37562b1b0deSchristos 
37662b1b0deSchristos 		case 'M':
377dd2f4acbSchristos 			if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
378dd2f4acbSchristos 				sc->sc_fill = NORMAL;
37962b1b0deSchristos 				++cp;
38062b1b0deSchristos 				slen--;
381dd2f4acbSchristos 			} else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
382dd2f4acbSchristos 				sc->sc_fill = LEGATO;
38362b1b0deSchristos 				++cp;
38462b1b0deSchristos 				slen--;
385dd2f4acbSchristos 			} else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's')) {
386dd2f4acbSchristos 				sc->sc_fill = STACCATO;
38762b1b0deSchristos 				++cp;
38862b1b0deSchristos 				slen--;
38962b1b0deSchristos 			}
39062b1b0deSchristos 			break;
39162b1b0deSchristos 		}
39262b1b0deSchristos 	}
39362b1b0deSchristos }
39462b1b0deSchristos 
3957a449eafSisaki /******************* UNIX DRIVER HOOKS BEGIN HERE **************************/
396dd2f4acbSchristos #define spkrenter(d)	device_lookup_private(&spkr_cd, d)
39762b1b0deSchristos 
3987a449eafSisaki /*
3997a449eafSisaki  * Attaches spkr.  Specify tone function with the following specification:
4007a449eafSisaki  *
4017a449eafSisaki  * void
4027a449eafSisaki  * tone(device_t self, u_int pitch, u_int tick)
4037a449eafSisaki  *	plays a beep with specified parameters.
4047a449eafSisaki  *	The argument 'pitch' specifies the pitch of a beep in Hz.  The argument
4057a449eafSisaki  *	'tick' specifies the period of a beep in tick(9).  This function waits
4067a449eafSisaki  *	to finish playing the beep and then halts it.
4077a449eafSisaki  *	If the pitch is zero, it halts all sound if any (for compatibility
4087a449eafSisaki  *	with the past confused specifications, but there should be no sound at
4097a449eafSisaki  *	this point).  And it returns immediately, without waiting ticks.  So
4107a449eafSisaki  *	you cannot use this as a rest.
4117a449eafSisaki  *	If the tick is zero, it returns immediately.
4127a449eafSisaki  */
413dd2f4acbSchristos void
4147a449eafSisaki spkr_attach(device_t self, void (*tone)(device_t, u_int, u_int))
41562b1b0deSchristos {
416dd2f4acbSchristos 	struct spkr_softc *sc = device_private(self);
417dd2f4acbSchristos 
418bccce848Spgoyette #ifdef SPKRDEBUG
419*e8b15cb7Sriastradh 	aprint_debug("%s: entering for unit %d\n", __func__,
420*e8b15cb7Sriastradh 	    device_unit(self));
421bccce848Spgoyette #endif /* SPKRDEBUG */
422dd2f4acbSchristos 	sc->sc_dev = self;
423dd2f4acbSchristos 	sc->sc_tone = tone;
424dd2f4acbSchristos 	sc->sc_inbuf = NULL;
42525c4f25dSpgoyette 	sc->sc_wsbelldev = NULL;
426e2f2d55bSnat 
4273bee0c11Sthorpej 	spkr_rescan(self, NULL, NULL);
42862b1b0deSchristos }
42962b1b0deSchristos 
43062b1b0deSchristos int
431bccce848Spgoyette spkr_detach(device_t self, int flags)
432bccce848Spgoyette {
433bccce848Spgoyette 	struct spkr_softc *sc = device_private(self);
4343e5646a1Snat 	int rc;
435bccce848Spgoyette 
436bccce848Spgoyette #ifdef SPKRDEBUG
437*e8b15cb7Sriastradh 	aprint_debug("%s: entering for unit %d\n", __func__,
438*e8b15cb7Sriastradh 	    device_unit(self));
439bccce848Spgoyette #endif /* SPKRDEBUG */
440bccce848Spgoyette 	if (sc == NULL)
441bccce848Spgoyette 		return ENXIO;
442a642e421Snat 
443a642e421Snat 	/* XXXNS If speaker never closes, we cannot complete the detach. */
444a642e421Snat 	while ((flags & DETACH_FORCE) != 0 && sc->sc_inbuf != NULL)
445a642e421Snat 		kpause("spkrwait", TRUE, 1, NULL);
446bccce848Spgoyette 	if (sc->sc_inbuf != NULL)
447bccce848Spgoyette 		return EBUSY;
448bccce848Spgoyette 
4493e5646a1Snat 	rc = config_detach_children(self, flags);
4503e5646a1Snat 
4513e5646a1Snat 	return rc;
452bccce848Spgoyette }
453bccce848Spgoyette 
45425c4f25dSpgoyette /* ARGSUSED */
45525c4f25dSpgoyette int
45625c4f25dSpgoyette spkr_rescan(device_t self, const char *iattr, const int *locators)
45725c4f25dSpgoyette {
45825c4f25dSpgoyette #if NWSMUX > 0
45925c4f25dSpgoyette 	struct spkr_softc *sc = device_private(self);
46025c4f25dSpgoyette 	struct wsbelldev_attach_args a;
46125c4f25dSpgoyette 
46225c4f25dSpgoyette 	if (sc->sc_wsbelldev == NULL) {
46325c4f25dSpgoyette 		a.accesscookie = sc;
4643bee0c11Sthorpej 		sc->sc_wsbelldev = config_found(self, &a, wsbelldevprint,
465beecddb6Sthorpej 		    CFARGS_NONE);
46625c4f25dSpgoyette 	}
46725c4f25dSpgoyette #endif
46825c4f25dSpgoyette 	return 0;
46925c4f25dSpgoyette }
47025c4f25dSpgoyette 
47125c4f25dSpgoyette int
47225c4f25dSpgoyette spkr_childdet(device_t self, device_t child)
47325c4f25dSpgoyette {
47425c4f25dSpgoyette 	struct spkr_softc *sc = device_private(self);
47525c4f25dSpgoyette 
47625c4f25dSpgoyette 	if (sc->sc_wsbelldev == child)
47725c4f25dSpgoyette 		sc->sc_wsbelldev = NULL;
47825c4f25dSpgoyette 
47925c4f25dSpgoyette 	return 0;
48025c4f25dSpgoyette }
48125c4f25dSpgoyette 
482bccce848Spgoyette int
48362b1b0deSchristos spkropen(dev_t dev, int	flags, int mode, struct lwp *l)
48462b1b0deSchristos {
485dd2f4acbSchristos 	struct spkr_softc *sc = spkrenter(minor(dev));
48662b1b0deSchristos 
487c195ab7cSisaki #ifdef SPKRDEBUG
488c195ab7cSisaki 	device_printf(sc->sc_dev, "%s: entering\n", __func__);
489c195ab7cSisaki #endif /* SPKRDEBUG */
490dd2f4acbSchristos 	if (sc == NULL)
491dd2f4acbSchristos 		return ENXIO;
492bccce848Spgoyette 	if (sc->sc_inbuf != NULL)
493dd2f4acbSchristos 		return EBUSY;
494dd2f4acbSchristos 
495dd2f4acbSchristos 	sc->sc_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK);
496dd2f4acbSchristos 	playinit(sc);
497dd2f4acbSchristos 	return 0;
49862b1b0deSchristos }
49962b1b0deSchristos 
50062b1b0deSchristos int
50162b1b0deSchristos spkrwrite(dev_t dev, struct uio *uio, int flags)
50262b1b0deSchristos {
503dd2f4acbSchristos 	struct spkr_softc *sc = spkrenter(minor(dev));
50462b1b0deSchristos 
505c195ab7cSisaki #ifdef SPKRDEBUG
506c195ab7cSisaki 	device_printf(sc->sc_dev, "%s: entering with length = %zu\n",
507c195ab7cSisaki 	    __func__, uio->uio_resid);
508c195ab7cSisaki #endif /* SPKRDEBUG */
509dd2f4acbSchristos 	if (sc == NULL)
510dd2f4acbSchristos 		return ENXIO;
511bccce848Spgoyette 	if (sc->sc_inbuf == NULL)
512dd2f4acbSchristos 		return EINVAL;
513dd2f4acbSchristos 
514a8a5c538Sriastradh 	size_t n = uimin(DEV_BSIZE, uio->uio_resid);
515dd2f4acbSchristos 	int error = uiomove(sc->sc_inbuf, n, uio);
516dd2f4acbSchristos 	if (error)
517dd2f4acbSchristos 		return error;
518dd2f4acbSchristos 	playstring(sc, sc->sc_inbuf, n);
519dd2f4acbSchristos 	return 0;
52062b1b0deSchristos }
52162b1b0deSchristos 
52262b1b0deSchristos int
52362b1b0deSchristos spkrclose(dev_t dev, int flags, int mode, struct lwp *l)
52462b1b0deSchristos {
525dd2f4acbSchristos 	struct spkr_softc *sc = spkrenter(minor(dev));
52662b1b0deSchristos 
527c195ab7cSisaki #ifdef SPKRDEBUG
528c195ab7cSisaki 	device_printf(sc->sc_dev, "%s: entering\n", __func__);
529c195ab7cSisaki #endif /* SPKRDEBUG */
530dd2f4acbSchristos 	if (sc == NULL)
531dd2f4acbSchristos 		return ENXIO;
532bccce848Spgoyette 	if (sc->sc_inbuf == NULL)
533dd2f4acbSchristos 		return EINVAL;
534dd2f4acbSchristos 
535dd2f4acbSchristos 	sc->sc_tone(sc->sc_dev, 0, 0);
536dd2f4acbSchristos 	free(sc->sc_inbuf, M_DEVBUF);
537dd2f4acbSchristos 	sc->sc_inbuf = NULL;
538dd2f4acbSchristos 
539dd2f4acbSchristos 	return 0;
54062b1b0deSchristos }
541dd2f4acbSchristos 
5427a449eafSisaki /*
5437a449eafSisaki  * Play tone specified by tp.
5447a449eafSisaki  * tp->frequency is the frequency (0 means a rest).
5457a449eafSisaki  * tp->duration is the length in tick (returns immediately if 0).
5467a449eafSisaki  */
547dd2f4acbSchristos static void
548dd2f4acbSchristos playonetone(struct spkr_softc *sc, tone_t *tp)
549dd2f4acbSchristos {
5507a449eafSisaki 	if (tp->duration <= 0)
5517a449eafSisaki 		return;
5527a449eafSisaki 
553dd2f4acbSchristos 	if (tp->frequency == 0)
5547a449eafSisaki 		rest(sc, tp->duration);
555dd2f4acbSchristos 	else
556dd2f4acbSchristos 		(*sc->sc_tone)(sc->sc_dev, tp->frequency, tp->duration);
55762b1b0deSchristos }
55862b1b0deSchristos 
55962b1b0deSchristos int
56062b1b0deSchristos spkrioctl(dev_t dev, u_long cmd, void *data, int flag, struct lwp *l)
56162b1b0deSchristos {
562c195ab7cSisaki 	struct spkr_softc *sc = spkrenter(minor(dev));
563dd2f4acbSchristos 	tone_t *tp;
56462b1b0deSchristos 	tone_t ttp;
56562b1b0deSchristos 	int error;
566c195ab7cSisaki 
567dd2f4acbSchristos #ifdef SPKRDEBUG
568c195ab7cSisaki 	device_printf(sc->sc_dev, "%s: entering with cmd = %lx\n",
569c195ab7cSisaki 	    __func__, cmd);
570dd2f4acbSchristos #endif /* SPKRDEBUG */
571dd2f4acbSchristos 	if (sc == NULL)
572dd2f4acbSchristos 		return ENXIO;
573bccce848Spgoyette 	if (sc->sc_inbuf == NULL)
574dd2f4acbSchristos 		return EINVAL;
575dd2f4acbSchristos 
576dd2f4acbSchristos 	switch (cmd) {
577dd2f4acbSchristos 	case SPKRTONE:
578dd2f4acbSchristos 		playonetone(sc, data);
579dd2f4acbSchristos 		return 0;
580dd2f4acbSchristos 	case SPKRTUNE:
581dd2f4acbSchristos 		for (tp = *(void **)data;; tp++) {
58262b1b0deSchristos 			error = copyin(tp, &ttp, sizeof(tone_t));
58362b1b0deSchristos 			if (error)
58462b1b0deSchristos 				return(error);
58562b1b0deSchristos 			if (ttp.duration == 0)
58662b1b0deSchristos 				break;
587dd2f4acbSchristos 			playonetone(sc, &ttp);
58862b1b0deSchristos 		}
589dd2f4acbSchristos 		return 0;
590bfcf5f24Snat 	case SPKRGETVOL:
591bfcf5f24Snat 		if (data != NULL)
592bfcf5f24Snat 			*(u_int *)data = sc->sc_vol;
593bfcf5f24Snat 		return 0;
594bfcf5f24Snat 	case SPKRSETVOL:
595bfcf5f24Snat 		if (data != NULL && *(u_int *)data <= 100)
596bfcf5f24Snat 			sc->sc_vol = *(u_int *)data;
597bfcf5f24Snat 		return 0;
598dd2f4acbSchristos 	default:
599dd2f4acbSchristos 		return ENOTTY;
60062b1b0deSchristos 	}
60162b1b0deSchristos }
60262b1b0deSchristos 
60346bee1b9Schristos #ifdef _MODULE
60446bee1b9Schristos #include "ioconf.c"
60546bee1b9Schristos #endif
60646bee1b9Schristos 
607f309403eSpgoyette MODULE(MODULE_CLASS_DRIVER, spkr, "audio" /* and/or pcppi */ );
608261a65d5Spgoyette 
60962b1b0deSchristos int
610261a65d5Spgoyette spkr_modcmd(modcmd_t cmd, void *arg)
61162b1b0deSchristos {
6126391aa4bSpgoyette 	int error = 0;
61362b1b0deSchristos #ifdef _MODULE
61462b1b0deSchristos 	devmajor_t bmajor, cmajor;
6156391aa4bSpgoyette #endif
61662b1b0deSchristos 
61762b1b0deSchristos 	switch(cmd) {
61862b1b0deSchristos 	case MODULE_CMD_INIT:
6196391aa4bSpgoyette #ifdef _MODULE
62062b1b0deSchristos 		bmajor = cmajor = -1;
62162b1b0deSchristos 		error = devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
62262b1b0deSchristos 		    &spkr_cdevsw, &cmajor);
62362b1b0deSchristos 		if (error)
62462b1b0deSchristos 			break;
62562b1b0deSchristos 
62662b1b0deSchristos 		error = config_init_component(cfdriver_ioconf_spkr,
62762b1b0deSchristos 		    cfattach_ioconf_spkr, cfdata_ioconf_spkr);
62862b1b0deSchristos 		if (error) {
62962b1b0deSchristos 			devsw_detach(NULL, &spkr_cdevsw);
63062b1b0deSchristos 		}
6316391aa4bSpgoyette #endif
63262b1b0deSchristos 		break;
63362b1b0deSchristos 
63462b1b0deSchristos 	case MODULE_CMD_FINI:
6356391aa4bSpgoyette #ifdef _MODULE
636bccce848Spgoyette 		devsw_detach(NULL, &spkr_cdevsw);
63762b1b0deSchristos 		error = config_fini_component(cfdriver_ioconf_spkr,
63862b1b0deSchristos 		    cfattach_ioconf_spkr, cfdata_ioconf_spkr);
639bccce848Spgoyette 		if (error)
640bccce848Spgoyette 			devsw_attach(spkr_cd.cd_name, NULL, &bmajor,
641bccce848Spgoyette 			    &spkr_cdevsw, &cmajor);
6426391aa4bSpgoyette #endif
64362b1b0deSchristos 		break;
644bccce848Spgoyette 
64562b1b0deSchristos 	default:
64662b1b0deSchristos 		error = ENOTTY;
64762b1b0deSchristos 		break;
64862b1b0deSchristos 	}
64962b1b0deSchristos 
65062b1b0deSchristos 	return error;
65162b1b0deSchristos }
652