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