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