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