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