xref: /freebsd/sys/dev/speaker/spkr.c (revision 4a0f765fbf09711e612e86fce8bb09ec43f482d9)
1 /*
2  * spkr.c -- device driver for console speaker
3  *
4  * v1.4 by Eric S. Raymond (esr@snark.thyrsus.com) Aug 1993
5  * modified for FreeBSD by Andrew A. Chernov <ache@astral.msk.su>
6  *
7  *    $Id$
8  */
9 
10 #include "speaker.h"
11 
12 #if NSPEAKER > 0
13 
14 #include <sys/param.h>
15 #include <sys/systm.h>
16 #include <sys/kernel.h>
17 #include <sys/buf.h>
18 #include <sys/uio.h>
19 #include <sys/conf.h>
20 #include <i386/isa/isa.h>
21 #include <i386/isa/timerreg.h>
22 #include <machine/clock.h>
23 #include <machine/speaker.h>
24 
25 #ifdef	DEVFS
26 #include <sys/devfsext.h>
27 void	*devfs_token;
28 #endif
29 
30 static	d_open_t	spkropen;
31 static	d_close_t	spkrclose;
32 static	d_write_t	spkrwrite;
33 static	d_ioctl_t	spkrioctl;
34 
35 #define CDEV_MAJOR 26
36 static struct cdevsw spkr_cdevsw =
37 	{ spkropen,     spkrclose,      noread,         spkrwrite,      /*26*/
38 	  spkrioctl,    nostop,         nullreset,      nodevtotty,/* spkr */
39 	  seltrue,	nommap,		NULL,	"spkr",	NULL,	-1 };
40 
41 /**************** MACHINE DEPENDENT PART STARTS HERE *************************
42  *
43  * This section defines a function tone() which causes a tone of given
44  * frequency and duration from the ISA console speaker.
45  * Another function endtone() is defined to force sound off, and there is
46  * also a rest() entry point to do pauses.
47  *
48  * Audible sound is generated using the Programmable Interval Timer (PIT) and
49  * Programmable Peripheral Interface (PPI) attached to the ISA speaker. The
50  * PPI controls whether sound is passed through at all; the PIT's channel 2 is
51  * used to generate clicks (a square wave) of whatever frequency is desired.
52  */
53 
54 /*
55  * PPI control values.
56  * XXX should be in a header and used in clock.c.
57  */
58 #define PPI_SPKR	0x03	/* turn these PPI bits on to pass sound */
59 
60 #define SPKRPRI PSOCK
61 static char endtone, endrest;
62 
63 static void tone __P((unsigned int thz, unsigned int ticks));
64 static void rest __P((int ticks));
65 static void playinit __P((void));
66 static void playtone __P((int pitch, int value, int sustain));
67 static int abs __P((int n));
68 static void playstring __P((char *cp, size_t slen));
69 
70 /* emit tone of frequency thz for given number of ticks */
71 static void
72 tone(thz, ticks)
73 	unsigned int thz, ticks;
74 {
75     unsigned int divisor;
76     int sps;
77 
78     if (thz <= 0)
79 	return;
80 
81     divisor = timer_freq / thz;
82 
83 #ifdef DEBUG
84     (void) printf("tone: thz=%d ticks=%d\n", thz, ticks);
85 #endif /* DEBUG */
86 
87     /* set timer to generate clicks at given frequency in Hertz */
88     sps = splclock();
89 
90     if (acquire_timer2(TIMER_SEL2 | TIMER_SQWAVE | TIMER_16BIT)) {
91 	/* enter list of waiting procs ??? */
92 	splx(sps);
93 	return;
94     }
95     splx(sps);
96     disable_intr();
97     outb(TIMER_CNTR2, (divisor & 0xff));	/* send lo byte */
98     outb(TIMER_CNTR2, (divisor >> 8));	/* send hi byte */
99     enable_intr();
100 
101     /* turn the speaker on */
102     outb(IO_PPI, inb(IO_PPI) | PPI_SPKR);
103 
104     /*
105      * Set timeout to endtone function, then give up the timeslice.
106      * This is so other processes can execute while the tone is being
107      * emitted.
108      */
109     if (ticks > 0)
110 	tsleep((caddr_t)&endtone, SPKRPRI | PCATCH, "spkrtn", ticks);
111     outb(IO_PPI, inb(IO_PPI) & ~PPI_SPKR);
112     sps = splclock();
113     release_timer2();
114     splx(sps);
115 }
116 
117 /* rest for given number of ticks */
118 static void
119 rest(ticks)
120 	int	ticks;
121 {
122     /*
123      * Set timeout to endrest function, then give up the timeslice.
124      * This is so other processes can execute while the rest is being
125      * waited out.
126      */
127 #ifdef DEBUG
128     (void) printf("rest: %d\n", ticks);
129 #endif /* DEBUG */
130     if (ticks > 0)
131 	tsleep((caddr_t)&endrest, SPKRPRI | PCATCH, "spkrrs", ticks);
132 }
133 
134 /**************** PLAY STRING INTERPRETER BEGINS HERE **********************
135  *
136  * Play string interpretation is modelled on IBM BASIC 2.0's PLAY statement;
137  * M[LNS] are missing; the ~ synonym and the _ slur mark and the octave-
138  * tracking facility are added.
139  * Requires tone(), rest(), and endtone(). String play is not interruptible
140  * except possibly at physical block boundaries.
141  */
142 
143 typedef int	bool;
144 #define TRUE	1
145 #define FALSE	0
146 
147 #define toupper(c)	((c) - ' ' * (((c) >= 'a') && ((c) <= 'z')))
148 #define isdigit(c)	(((c) >= '0') && ((c) <= '9'))
149 #define dtoi(c)		((c) - '0')
150 
151 static int octave;	/* currently selected octave */
152 static int whole;	/* whole-note time at current tempo, in ticks */
153 static int value;	/* whole divisor for note time, quarter note = 1 */
154 static int fill;	/* controls spacing of notes */
155 static bool octtrack;	/* octave-tracking on? */
156 static bool octprefix;	/* override current octave-tracking state? */
157 
158 /*
159  * Magic number avoidance...
160  */
161 #define SECS_PER_MIN	60	/* seconds per minute */
162 #define WHOLE_NOTE	4	/* quarter notes per whole note */
163 #define MIN_VALUE	64	/* the most we can divide a note by */
164 #define DFLT_VALUE	4	/* default value (quarter-note) */
165 #define FILLTIME	8	/* for articulation, break note in parts */
166 #define STACCATO	6	/* 6/8 = 3/4 of note is filled */
167 #define NORMAL		7	/* 7/8ths of note interval is filled */
168 #define LEGATO		8	/* all of note interval is filled */
169 #define DFLT_OCTAVE	4	/* default octave */
170 #define MIN_TEMPO	32	/* minimum tempo */
171 #define DFLT_TEMPO	120	/* default tempo */
172 #define MAX_TEMPO	255	/* max tempo */
173 #define NUM_MULT	3	/* numerator of dot multiplier */
174 #define DENOM_MULT	2	/* denominator of dot multiplier */
175 
176 /* letter to half-tone:  A   B  C  D  E  F  G */
177 static int notetab[8] = {9, 11, 0, 2, 4, 5, 7};
178 
179 /*
180  * This is the American Standard A440 Equal-Tempered scale with frequencies
181  * rounded to nearest integer. Thank Goddess for the good ol' CRC Handbook...
182  * our octave 0 is standard octave 2.
183  */
184 #define OCTAVE_NOTES	12	/* semitones per octave */
185 static int pitchtab[] =
186 {
187 /*        C     C#    D     D#    E     F     F#    G     G#    A     A#    B*/
188 /* 0 */   65,   69,   73,   78,   82,   87,   93,   98,  103,  110,  117,  123,
189 /* 1 */  131,  139,  147,  156,  165,  175,  185,  196,  208,  220,  233,  247,
190 /* 2 */  262,  277,  294,  311,  330,  349,  370,  392,  415,  440,  466,  494,
191 /* 3 */  523,  554,  587,  622,  659,  698,  740,  784,  831,  880,  932,  988,
192 /* 4 */ 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1975,
193 /* 5 */ 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951,
194 /* 6 */ 4186, 4435, 4698, 4978, 5274, 5588, 5920, 6272, 6644, 7040, 7459, 7902,
195 };
196 
197 static void
198 playinit()
199 {
200     octave = DFLT_OCTAVE;
201     whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / DFLT_TEMPO;
202     fill = NORMAL;
203     value = DFLT_VALUE;
204     octtrack = FALSE;
205     octprefix = TRUE;	/* act as though there was an initial O(n) */
206 }
207 
208 /* play tone of proper duration for current rhythm signature */
209 static void
210 playtone(pitch, value, sustain)
211 	int	pitch, value, sustain;
212 {
213     register int	sound, silence, snum = 1, sdenom = 1;
214 
215     /* this weirdness avoids floating-point arithmetic */
216     for (; sustain; sustain--)
217     {
218 	/* See the BUGS section in the man page for discussion */
219 	snum *= NUM_MULT;
220 	sdenom *= DENOM_MULT;
221     }
222 
223     if (value == 0 || sdenom == 0)
224 	return;
225 
226     if (pitch == -1)
227 	rest(whole * snum / (value * sdenom));
228     else
229     {
230 	sound = (whole * snum) / (value * sdenom)
231 		- (whole * (FILLTIME - fill)) / (value * FILLTIME);
232 	silence = whole * (FILLTIME-fill) * snum / (FILLTIME * value * sdenom);
233 
234 #ifdef DEBUG
235 	(void) printf("playtone: pitch %d for %d ticks, rest for %d ticks\n",
236 			pitch, sound, silence);
237 #endif /* DEBUG */
238 
239 	tone(pitchtab[pitch], sound);
240 	if (fill != LEGATO)
241 	    rest(silence);
242     }
243 }
244 
245 static int
246 abs(n)
247 	int n;
248 {
249     if (n < 0)
250 	return(-n);
251     else
252 	return(n);
253 }
254 
255 /* interpret and play an item from a notation string */
256 static void
257 playstring(cp, slen)
258 	char	*cp;
259 	size_t	slen;
260 {
261     int		pitch, oldfill, lastpitch = OCTAVE_NOTES * DFLT_OCTAVE;
262 
263 #define GETNUM(cp, v)	for(v=0; isdigit(cp[1]) && slen > 0; ) \
264 				{v = v * 10 + (*++cp - '0'); slen--;}
265     for (; slen--; cp++)
266     {
267 	int		sustain, timeval, tempo;
268 	register char	c = toupper(*cp);
269 
270 #ifdef DEBUG
271 	(void) printf("playstring: %c (%x)\n", c, c);
272 #endif /* DEBUG */
273 
274 	switch (c)
275 	{
276 	case 'A':  case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
277 
278 	    /* compute pitch */
279 	    pitch = notetab[c - 'A'] + octave * OCTAVE_NOTES;
280 
281 	    /* this may be followed by an accidental sign */
282 	    if (cp[1] == '#' || cp[1] == '+')
283 	    {
284 		++pitch;
285 		++cp;
286 		slen--;
287 	    }
288 	    else if (cp[1] == '-')
289 	    {
290 		--pitch;
291 		++cp;
292 		slen--;
293 	    }
294 
295 	    /*
296 	     * If octave-tracking mode is on, and there has been no octave-
297 	     * setting prefix, find the version of the current letter note
298 	     * closest to the last regardless of octave.
299 	     */
300 	    if (octtrack && !octprefix)
301 	    {
302 		if (abs(pitch-lastpitch) > abs(pitch+OCTAVE_NOTES-lastpitch))
303 		{
304 		    ++octave;
305 		    pitch += OCTAVE_NOTES;
306 		}
307 
308 		if (abs(pitch-lastpitch) > abs((pitch-OCTAVE_NOTES)-lastpitch))
309 		{
310 		    --octave;
311 		    pitch -= OCTAVE_NOTES;
312 		}
313 	    }
314 	    octprefix = FALSE;
315 	    lastpitch = pitch;
316 
317 	    /* ...which may in turn be followed by an override time value */
318 	    GETNUM(cp, timeval);
319 	    if (timeval <= 0 || timeval > MIN_VALUE)
320 		timeval = value;
321 
322 	    /* ...and/or sustain dots */
323 	    for (sustain = 0; cp[1] == '.'; cp++)
324 	    {
325 		slen--;
326 		sustain++;
327 	    }
328 
329 	    /* ...and/or a slur mark */
330 	    oldfill = fill;
331 	    if (cp[1] == '_')
332 	    {
333 		fill = LEGATO;
334 		++cp;
335 		slen--;
336 	    }
337 
338 	    /* time to emit the actual tone */
339 	    playtone(pitch, timeval, sustain);
340 
341 	    fill = oldfill;
342 	    break;
343 
344 	case 'O':
345 	    if (cp[1] == 'N' || cp[1] == 'n')
346 	    {
347 		octprefix = octtrack = FALSE;
348 		++cp;
349 		slen--;
350 	    }
351 	    else if (cp[1] == 'L' || cp[1] == 'l')
352 	    {
353 		octtrack = TRUE;
354 		++cp;
355 		slen--;
356 	    }
357 	    else
358 	    {
359 		GETNUM(cp, octave);
360 		if (octave >= sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES)
361 		    octave = DFLT_OCTAVE;
362 		octprefix = TRUE;
363 	    }
364 	    break;
365 
366 	case '>':
367 	    if (octave < sizeof(pitchtab) / sizeof(pitchtab[0]) / OCTAVE_NOTES - 1)
368 		octave++;
369 	    octprefix = TRUE;
370 	    break;
371 
372 	case '<':
373 	    if (octave > 0)
374 		octave--;
375 	    octprefix = TRUE;
376 	    break;
377 
378 	case 'N':
379 	    GETNUM(cp, pitch);
380 	    for (sustain = 0; cp[1] == '.'; cp++)
381 	    {
382 		slen--;
383 		sustain++;
384 	    }
385 	    oldfill = fill;
386 	    if (cp[1] == '_')
387 	    {
388 		fill = LEGATO;
389 		++cp;
390 		slen--;
391 	    }
392 	    playtone(pitch - 1, value, sustain);
393 	    fill = oldfill;
394 	    break;
395 
396 	case 'L':
397 	    GETNUM(cp, value);
398 	    if (value <= 0 || value > MIN_VALUE)
399 		value = DFLT_VALUE;
400 	    break;
401 
402 	case 'P':
403 	case '~':
404 	    /* this may be followed by an override time value */
405 	    GETNUM(cp, timeval);
406 	    if (timeval <= 0 || timeval > MIN_VALUE)
407 		timeval = value;
408 	    for (sustain = 0; cp[1] == '.'; cp++)
409 	    {
410 		slen--;
411 		sustain++;
412 	    }
413 	    playtone(-1, timeval, sustain);
414 	    break;
415 
416 	case 'T':
417 	    GETNUM(cp, tempo);
418 	    if (tempo < MIN_TEMPO || tempo > MAX_TEMPO)
419 		tempo = DFLT_TEMPO;
420 	    whole = (hz * SECS_PER_MIN * WHOLE_NOTE) / tempo;
421 	    break;
422 
423 	case 'M':
424 	    if (cp[1] == 'N' || cp[1] == 'n')
425 	    {
426 		fill = NORMAL;
427 		++cp;
428 		slen--;
429 	    }
430 	    else if (cp[1] == 'L' || cp[1] == 'l')
431 	    {
432 		fill = LEGATO;
433 		++cp;
434 		slen--;
435 	    }
436 	    else if (cp[1] == 'S' || cp[1] == 's')
437 	    {
438 		fill = STACCATO;
439 		++cp;
440 		slen--;
441 	    }
442 	    break;
443 	}
444     }
445 }
446 
447 /******************* UNIX DRIVER HOOKS BEGIN HERE **************************
448  *
449  * This section implements driver hooks to run playstring() and the tone(),
450  * endtone(), and rest() functions defined above.
451  */
452 
453 static int spkr_active = FALSE; /* exclusion flag */
454 static struct buf *spkr_inbuf;  /* incoming buf */
455 
456 int
457 spkropen(dev, flags, fmt, p)
458 	dev_t		dev;
459 	int		flags;
460 	int		fmt;
461 	struct proc	*p;
462 {
463 #ifdef DEBUG
464     (void) printf("spkropen: entering with dev = %x\n", dev);
465 #endif /* DEBUG */
466 
467     if (minor(dev) != 0)
468 	return(ENXIO);
469     else if (spkr_active)
470 	return(EBUSY);
471     else
472     {
473 #ifdef DEBUG
474 	(void) printf("spkropen: about to perform play initialization\n");
475 #endif /* DEBUG */
476 	playinit();
477 	spkr_inbuf = geteblk(DEV_BSIZE);
478 	spkr_active = TRUE;
479 	return(0);
480     }
481 }
482 
483 int
484 spkrwrite(dev, uio, ioflag)
485 	dev_t		dev;
486 	struct uio	*uio;
487 	int		ioflag;
488 {
489 #ifdef DEBUG
490     printf("spkrwrite: entering with dev = %x, count = %d\n",
491 		dev, uio->uio_resid);
492 #endif /* DEBUG */
493 
494     if (minor(dev) != 0)
495 	return(ENXIO);
496     else if (uio->uio_resid > (DEV_BSIZE - 1))     /* prevent system crashes */
497 	return(E2BIG);
498     else
499     {
500 	unsigned n;
501 	char *cp;
502 	int error;
503 
504 	n = uio->uio_resid;
505 	cp = spkr_inbuf->b_un.b_addr;
506 	error = uiomove(cp, n, uio);
507 	if (!error) {
508 		cp[n] = '\0';
509 		playstring(cp, n);
510 	}
511 	return(error);
512     }
513 }
514 
515 int
516 spkrclose(dev, flags, fmt, p)
517 	dev_t		dev;
518 	int		flags;
519 	int		fmt;
520 	struct proc	*p;
521 {
522 #ifdef DEBUG
523     (void) printf("spkrclose: entering with dev = %x\n", dev);
524 #endif /* DEBUG */
525 
526     if (minor(dev) != 0)
527 	return(ENXIO);
528     else
529     {
530 	wakeup((caddr_t)&endtone);
531 	wakeup((caddr_t)&endrest);
532 	brelse(spkr_inbuf);
533 	spkr_active = FALSE;
534 	return(0);
535     }
536 }
537 
538 int
539 spkrioctl(dev, cmd, cmdarg, flags, p)
540 	dev_t		dev;
541 	int		cmd;
542 	caddr_t		cmdarg;
543 	int		flags;
544 	struct proc	*p;
545 {
546 #ifdef DEBUG
547     (void) printf("spkrioctl: entering with dev = %x, cmd = %x\n");
548 #endif /* DEBUG */
549 
550     if (minor(dev) != 0)
551 	return(ENXIO);
552     else if (cmd == SPKRTONE)
553     {
554 	tone_t	*tp = (tone_t *)cmdarg;
555 
556 	if (tp->frequency == 0)
557 	    rest(tp->duration);
558 	else
559 	    tone(tp->frequency, tp->duration);
560 	return 0;
561     }
562     else if (cmd == SPKRTUNE)
563     {
564 	tone_t  *tp = (tone_t *)(*(caddr_t *)cmdarg);
565 	tone_t ttp;
566 	int error;
567 
568 	for (; ; tp++) {
569 	    error = copyin(tp, &ttp, sizeof(tone_t));
570 	    if (error)
571 		    return(error);
572 	    if (ttp.duration == 0)
573 		    break;
574 	    if (ttp.frequency == 0)
575 		 rest(ttp.duration);
576 	    else
577 		 tone(ttp.frequency, ttp.duration);
578 	}
579 	return(0);
580     }
581     return(EINVAL);
582 }
583 
584 
585 static spkr_devsw_installed = 0;
586 
587 static void
588 spkr_drvinit(void *unused)
589 {
590 	dev_t dev;
591 
592 	if( ! spkr_devsw_installed ) {
593 		dev = makedev(CDEV_MAJOR, 0);
594 		cdevsw_add(&dev,&spkr_cdevsw, NULL);
595 		spkr_devsw_installed = 1;
596 #ifdef DEVFS
597 		devfs_token = devfs_add_devswf(&spkr_cdevsw, 0, DV_CHR,
598 					       UID_ROOT, GID_WHEEL, 0600,
599 					       "speaker");
600 #endif
601     	}
602 }
603 
604 SYSINIT(spkrdev,SI_SUB_DRIVERS,SI_ORDER_MIDDLE+CDEV_MAJOR,spkr_drvinit,NULL)
605 
606 
607 #endif  /* NSPEAKER > 0 */
608 /* spkr.c ends here */
609