1 /**	$MirOS: src/sys/dev/isa/spkr.c,v 1.3 2008/11/08 23:04:19 tg Exp $ */
2 /*	$OpenBSD: spkr.c,v 1.7 2002/03/14 01:26:56 millert Exp $	*/
3 /*	$NetBSD: spkr.c,v 1.1 1998/04/15 20:26:18 drochner Exp $	*/
4 
5 /*
6  * Copyright (c) 1990 Eric S. Raymond (esr@snark.thyrsus.com)
7  * Copyright (c) 1990 Andrew A. Chernov (ache@astral.msk.su)
8  * Copyright (c) 1990 Lennart Augustsson (lennart@augustsson.net)
9  * Copyright (c) 2002 Thorsten "mirabilos" Glaser (tg@mirbsd.org)
10  * All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. All advertising materials mentioning features or use of this software
21  *    must display the following acknowledgement:
22  *	This product includes software developed by Eric S. Raymond
23  * 4. The name of the author may not be used to endorse or promote products
24  *    derived from this software without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
27  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29  * DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
30  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  */
38 
39 /*
40  * spkr.c -- device driver for console speaker on 80386
41  *
42  * v1.1 by Eric S. Raymond (esr@snark.thyrsus.com) Feb 1990
43  *      modified for 386bsd by Andrew A. Chernov <ache@astral.msk.su>
44  *      386bsd only clean version, all SYSV stuff removed
45  *      use hz value from param.c
46  */
47 
48 #include <sys/param.h>
49 #include <sys/systm.h>
50 #include <sys/kernel.h>
51 #include <sys/errno.h>
52 #include <sys/device.h>
53 #include <sys/malloc.h>
54 #include <sys/uio.h>
55 #include <sys/proc.h>
56 #include <sys/ioctl.h>
57 #include <sys/conf.h>
58 
59 #include <dev/isa/pcppivar.h>
60 
61 #include <dev/isa/spkrio.h>
62 
63 cdev_decl(spkr);
64 
65 #define __BROKEN_INDIRECT_CONFIG /* XXX */
66 #ifdef __BROKEN_INDIRECT_CONFIG
67 int spkrprobe(struct device *, void *, void *);
68 #else
69 int spkrprobe(struct device *, struct cfdata *, void *);
70 #endif
71 void spkrattach(struct device *, struct device *, void *);
72 
73 struct spkr_softc {
74 	struct device sc_dev;
75 };
76 
77 struct cfattach spkr_ca = {
78 	sizeof(struct spkr_softc), spkrprobe, spkrattach
79 };
80 
81 struct cfdriver spkr_cd = {
82 	NULL, "spkr", DV_DULL
83 };
84 
85 static pcppi_tag_t ppicookie;
86 
87 #define SPKRPRI (PZERO - 1)
88 
89 static void tone(u_int, u_int);
90 static void rest(int);
91 static void playinit(void);
92 static void playtone(int, int, int);
93 static void playstring(char *, int);
94 
95 /* emit tone of frequency hz for given number of ticks */
96 static
tone(u_int hz,u_int ticks)97 void tone(u_int hz, u_int ticks)
98 {
99 	pcppi_bell(ppicookie, hz, ticks, PCPPI_BELL_SLEEP);
100 }
101 
102 /* rest for given number of ticks */
103 static void
rest(int ticks)104 rest(int ticks)
105 {
106     /*
107      * Set timeout to endrest function, then give up the timeslice.
108      * This is so other processes can execute while the rest is being
109      * waited out.
110      */
111 #ifdef SPKRDEBUG
112     printf("rest: %d\n", ticks);
113 #endif /* SPKRDEBUG */
114     if (ticks > 0)
115 	    tsleep(rest, SPKRPRI | PCATCH, "rest", ticks);
116 }
117 
118 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
119  *
120  * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
121  * M[LNS] are missing and the ~ synonym and octave-tracking facility is added.
122  * Requires tone(), rest(), and endtone(). String play is not interruptible
123  * except possibly at physical block boundaries.
124  */
125 
126 typedef int	bool;
127 #define TRUE	1
128 #define FALSE	0
129 
130 #define toupper(c)	((c) - ' ' * (((c) >= 'a') && ((c) <= 'z')))
131 #define isdigit(c)	(((c) >= '0') && ((c) <= '9'))
132 #define dtoi(c)		((c) - '0')
133 
134 static int octave;	/* currently selected octave */
135 static int whole;	/* whole-note time at current tempo, in ticks */
136 static int value;	/* whole divisor for note time, quarter note = 1 */
137 static int fill;	/* controls spacing of notes */
138 static bool octtrack;	/* octave-tracking on? */
139 static bool octprefix;	/* override current octave-tracking state? */
140 
141 /*
142  * Magic number avoidance...
143  */
144 #define SECS_PER_MIN	60	/* seconds per minute */
145 #define WHOLE_NOTE	4	/* quarter notes per whole note */
146 #define MIN_VALUE	64	/* the most we can divide a note by */
147 #define DFLT_VALUE	4	/* default value (quarter-note) */
148 #define FILLTIME	8	/* for articulation, break note in parts */
149 #define STACCATO	6	/* 6/8 = 3/4 of note is filled */
150 #define NORMAL		7	/* 7/8ths of note interval is filled */
151 #define LEGATO		8	/* all of note interval is filled */
152 #define DFLT_OCTAVE	4	/* default octave */
153 #define MIN_TEMPO	32	/* minimum tempo */
154 #define DFLT_TEMPO	120	/* default tempo */
155 #define MAX_TEMPO	255	/* max tempo */
156 #define NUM_MULT	3	/* numerator of dot multiplier */
157 #define DENOM_MULT	2	/* denominator of dot multiplier */
158 
159 /* letter to half-tone:  A   B  C  D  E  F  G */
160 static int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
161 
162 /*
163  * This is the American Standard A440 Equal-Tempered scale with frequencies
164  * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
165  * our octave 0 is standard octave 2.
166  */
167 #define OCTAVE_NOTES	12	/* semitones per octave */
168 static int pitchtab[] =
169 {
170 /*        C     C#    D     D#    E     F     F#    G     G#    A     A#    B*/
171 /* 0 */   65,   69,   73,   78,   82,   87,   93,   98,  103,  110,  117,  123,
172 /* 1 */  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,
173 /* 2 */  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,
174 /* 3 */  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,
175 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
176 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
177 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
178 };
179 #define NOCTAVES (sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES)
180 
181 static void
playinit(void)182 playinit(void)
183 {
184     octave = DFLT_OCTAVE;
185     whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
186     fill = NORMAL;
187     value = DFLT_VALUE;
188     octtrack = FALSE;
189     octprefix = TRUE;	/* act as though there was an initial O(n) */
190 }
191 
192 /* play tone of proper duration for current rhythm signature */
193 static void
playtone(int pitch,int value,int sustain)194 playtone(int pitch, int value, int sustain)
195 {
196     int sound, silence, snum = 1, sdenom = 1;
197 
198     if (!value) return;
199 
200     /* this weirdness avoids floating-point arithmetic */
201     while (sustain--) {
202 	snum *= NUM_MULT;
203 	sdenom *= DENOM_MULT;
204     }
205 
206     if (pitch == -1)
207 	rest(whole * snum / (value * sdenom));
208     else if ((pitch>=0)&&(pitch < (sizeof(pitchtab)/sizeof(pitchtab[0])) )) {
209 	sound = (whole * snum) / (value * sdenom)
210 		- (whole * (FILLTIME - fill)) / (value * FILLTIME);
211 	silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom);
212 
213 #ifdef SPKRDEBUG
214 	printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
215 	    pitch, sound, silence);
216 #endif /* SPKRDEBUG */
217 
218 	tone(pitchtab[pitch], sound);
219 	if (fill != LEGATO)
220 	    rest(silence);
221     }
222 }
223 
224 /* interpret and play an item from a notation string */
225 static void
playstring(char * cp,int slen)226 playstring(char *cp, int slen)
227 {
228     int pitch, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
229 
230 #define GETNUM(cp, v)	for(v=0; slen > 0 && isdigit(cp[1]); slen--) \
231 				v = v * 10 + (*++cp - '0');
232     for (; slen--; cp++) {
233 	int sustain, timeval, tempo;
234 	char c = toupper(*cp);
235 
236 #ifdef SPKRDEBUG
237 	printf("playstring: %c (%x)\n", c, c);
238 #endif /* SPKRDEBUG */
239 
240 	switch (c) {
241 	  case 'A':  case 'B':  case 'C':  case 'D':
242 	  case 'E':  case 'F':  case 'G':
243 
244 	    /* compute pitch */
245 	    pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
246 
247 	    /* this may be followed by an accidental sign */
248 	    if (slen > 0 && (cp[1] == '#' || cp[1] == '+')) {
249 		++pitch;
250 		++cp;
251 		slen--;
252 	    } else if (slen > 0 && cp[1] == '-') {
253 		--pitch;
254 		++cp;
255 		slen--;
256 	    }
257 
258 	    /*
259 	     * If octave-tracking mode is on, and there has been no octave-
260 	     * setting prefix, find the version of the current letter note
261 	     * closest to the last regardless of octave.
262 	     */
263 	    if (octtrack && !octprefix) {
264 		if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch)) {
265 		    ++octave;
266 		    pitch += OCTAVE_NOTES;
267 		}
268 
269 		if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch)) {
270 		    --octave;
271 		    pitch -= OCTAVE_NOTES;
272 		}
273 	    }
274 	    octprefix = FALSE;
275 	    lastpitch = pitch;
276 
277 	    /* ...which may in turn be followed by an override time value */
278 	    GETNUM(cp, timeval)
279 	    if (timeval <= 0 || timeval > MIN_VALUE)
280 		timeval = value;
281 
282 	    /* ...and/or sustain dots */
283 	    for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
284 		slen--;
285 		sustain++;
286 	    }
287 
288 	    /* time to emit the actual tone */
289 	    playtone(pitch, timeval, sustain);
290 	    break;
291 
292 	case 'O':
293 	    if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
294 		octprefix = octtrack = FALSE;
295 		++cp;
296 		slen--;
297 	    } else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
298 		octtrack = TRUE;
299 		++cp;
300 		slen--;
301 	    } else if (slen > 0) {
302 		GETNUM(cp, octave)
303 		if (octave >= NOCTAVES)
304 		    octave = DFLT_OCTAVE;
305 		octprefix = TRUE;
306 	    }
307 	    break;
308 
309 	case '>':
310 	    if (octave < NOCTAVES - 1)
311 		octave++;
312 	    octprefix = TRUE;
313 	    break;
314 
315 	case '<':
316 	    if (octave > 0)
317 		octave--;
318 	    octprefix = TRUE;
319 	    break;
320 
321 	case 'N':
322 	    GETNUM(cp, pitch)
323 	    for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
324 		slen--;
325 		sustain++;
326 	    }
327 	    playtone(pitch - 1, value, sustain);
328 	    break;
329 
330 	case 'L':
331 	    GETNUM(cp, value)
332 	    if (!value || value > MIN_VALUE)
333 		value = DFLT_VALUE;
334 	    break;
335 
336 	case 'P':  case '~':
337 	    /* this may be followed by an override time value */
338 	    GETNUM(cp, timeval)
339 	    if (!timeval || timeval > MIN_VALUE)
340 		timeval = value;
341 	    for (sustain = 0; slen > 0 && cp[1] == '.'; cp++) {
342 		slen--;
343 		sustain++;
344 	    }
345 	    playtone(-1, timeval, sustain);
346 	    break;
347 
348 	case 'T':
349 	    GETNUM(cp, tempo)
350 	    if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
351 		tempo = DFLT_TEMPO;
352 	    whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
353 	    break;
354 
355 	case 'M':
356 	    if (slen > 0 && (cp[1] == 'N' || cp[1] == 'n')) {
357 		fill = NORMAL;
358 		++cp;
359 		slen--;
360 	    } else if (slen > 0 && (cp[1] == 'L' || cp[1] == 'l')) {
361 		fill = LEGATO;
362 		++cp;
363 		slen--;
364 	    } else if (slen > 0 && (cp[1] == 'S' || cp[1] == 's')) {
365 		fill = STACCATO;
366 		++cp;
367 		slen--;
368 	    }
369 	    /* intentionally no support for MF/MB (Foreground/Background) */
370 	    break;
371 	}
372     }
373 }
374 
375 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
376  *
377  * This section implements driver hooks to run playstring() and the tone(),
378  * endtone(), and rest() functions defined above.
379  */
380 
381 static int spkr_active;	/* exclusion flag */
382 static void *spkr_inbuf;
383 
384 static int spkr_attached = 0;
385 
386 int
387 #ifdef __BROKEN_INDIRECT_CONFIG
spkrprobe(struct device * parent,void * match,void * aux)388 spkrprobe (struct device *parent, void *match, void *aux)
389 #else
390 spkrprobe (struct device *parent, struct cfdata *match, void *aux)
391 #endif
392 {
393 	return (!spkr_attached);
394 }
395 
396 void
spkrattach(struct device * parent,struct device * self,void * aux)397 spkrattach(struct device *parent, struct device *self, void *aux)
398 {
399 	printf("\n");
400 	ppicookie = ((struct pcppi_attach_args *)aux)->pa_cookie;
401 	spkr_attached = 1;
402 }
403 
404 int
spkropen(dev_t dev,int flags,int mode,struct proc * p)405 spkropen(dev_t dev, int flags, int mode, struct proc *p)
406 {
407 #ifdef SPKRDEBUG
408     printf("spkropen: entering with dev = %x\n", dev);
409 #endif /* SPKRDEBUG */
410 
411     if (minor(dev) != 0 || !spkr_attached)
412 	return (ENXIO);
413     else if (spkr_active)
414 	return (EBUSY);
415     else {
416 	playinit();
417 	spkr_inbuf = malloc(DEV_BSIZE, M_DEVBUF, M_WAITOK);
418 	spkr_active = 1;
419     }
420     return (0);
421 }
422 
423 int
spkrwrite(dev_t dev,struct uio * uio,int flags)424 spkrwrite(dev_t dev, struct uio *uio, int flags)
425 {
426     int n;
427     int error;
428 #ifdef SPKRDEBUG
429     printf("spkrwrite: entering with dev = %x, count = %d\n",
430 		dev, uio->uio_resid);
431 #endif /* SPKRDEBUG */
432 
433     if (minor(dev) != 0)
434 	return (ENXIO);
435     else {
436 	n = min(DEV_BSIZE, uio->uio_resid);
437 	error = uiomove(spkr_inbuf, n, uio);
438 	if (!error)
439 		playstring((char *)spkr_inbuf, n);
440 	return (error);
441     }
442 }
443 
spkrclose(dev_t dev,int flags,int mode,struct proc * p)444 int spkrclose(dev_t dev, int flags, int mode, struct proc *p)
445 {
446 #ifdef SPKRDEBUG
447     printf("spkrclose: entering with dev = %x\n", dev);
448 #endif /* SPKRDEBUG */
449 
450     if (minor(dev) != 0)
451 	return (ENXIO);
452     else {
453 	tone(0, 0);
454 	free(spkr_inbuf, M_DEVBUF);
455 	spkr_active = 0;
456     }
457     return (0);
458 }
459 
spkrioctl(dev_t dev,u_long cmd,caddr_t data,int flag,struct proc * p)460 int spkrioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
461 {
462 #ifdef SPKRDEBUG
463     printf("spkrioctl: entering with dev = %x, cmd = %lx\n", dev, cmd);
464 #endif /* SPKRDEBUG */
465 
466     if (minor(dev) != 0)
467 	return (ENXIO);
468     else if (cmd == SPKRTONE) {
469 	tone_t	*tp = (tone_t *)data;
470 	if (!tp->frequency)
471 	    rest(tp->duration);
472 	else tone(tp->frequency, tp->duration);
473     } else if (cmd == SPKRTUNE) {
474 	tone_t *tp = (tone_t *)(*(caddr_t *)data);
475 	tone_t ttp;
476 	int error;
477 
478 	for (; ; tp++) {
479 	    error = copyin(tp, &ttp, sizeof(tone_t));
480 	    if (error)
481 		    return (error);
482 	    if (!ttp.duration)
483 		    break;
484 	    if (!ttp.frequency)
485 		rest(ttp.duration);
486 	    else tone(ttp.frequency, ttp.duration);
487 	}
488     } else
489 	return (EINVAL);
490     return (0);
491 }
492