spkr.c revision 10537
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.14 1995/05/30 08:03:09 rgrimes 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(caddr_t 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)
455dev_t	dev;
456{
457#ifdef DEBUG
458    (void) printf("spkropen: entering with dev = %x\n", dev);
459#endif /* DEBUG */
460
461    if (minor(dev) != 0)
462	return(ENXIO);
463    else if (spkr_active)
464	return(EBUSY);
465    else
466    {
467#ifdef DEBUG
468	(void) printf("spkropen: about to perform play initialization\n");
469#endif /* DEBUG */
470	playinit();
471	spkr_inbuf = geteblk(DEV_BSIZE);
472	spkr_active = TRUE;
473	return(0);
474    }
475}
476
477int spkrwrite(dev, uio)
478dev_t		dev;
479struct uio	*uio;
480{
481#ifdef DEBUG
482    printf("spkrwrite: entering with dev = %x, count = %d\n",
483		dev, uio->uio_resid);
484#endif /* DEBUG */
485
486    if (minor(dev) != 0)
487	return(ENXIO);
488    else if (uio->uio_resid > DEV_BSIZE)     /* prevent system crashes */
489	return(E2BIG);
490    else
491    {
492	unsigned n;
493	char *cp;
494	int error;
495
496	n = uio->uio_resid;
497	cp = spkr_inbuf->b_un.b_addr;
498	if (!(error = uiomove(cp, n, uio)))
499		playstring(cp, n);
500	return(error);
501    }
502}
503
504int spkrclose(dev)
505dev_t	dev;
506{
507#ifdef DEBUG
508    (void) printf("spkrclose: entering with dev = %x\n", dev);
509#endif /* DEBUG */
510
511    if (minor(dev) != 0)
512	return(ENXIO);
513    else
514    {
515	wakeup((caddr_t)&endtone);
516	wakeup((caddr_t)&endrest);
517	brelse(spkr_inbuf);
518	spkr_active = FALSE;
519	return(0);
520    }
521}
522
523int spkrioctl(dev, cmd, cmdarg)
524dev_t	dev;
525int	cmd;
526caddr_t	cmdarg;
527{
528#ifdef DEBUG
529    (void) printf("spkrioctl: entering with dev = %x, cmd = %x\n");
530#endif /* DEBUG */
531
532    if (minor(dev) != 0)
533	return(ENXIO);
534    else if (cmd == SPKRTONE)
535    {
536	tone_t	*tp = (tone_t *)cmdarg;
537
538	if (tp->frequency == 0)
539	    rest(tp->duration);
540	else
541	    tone(tp->frequency, tp->duration);
542	return 0;
543    }
544    else if (cmd == SPKRTUNE)
545    {
546	tone_t  *tp = (tone_t *)(*(caddr_t *)cmdarg);
547	tone_t ttp;
548	int error;
549
550	for (; ; tp++) {
551	    error = copyin(tp, &ttp, sizeof(tone_t));
552	    if (error)
553		    return(error);
554	    if (ttp.duration == 0)
555		    break;
556	    if (ttp.frequency == 0)
557		 rest(ttp.duration);
558	    else
559		 tone(ttp.frequency, ttp.duration);
560	}
561	return(0);
562    }
563    return(EINVAL);
564}
565
566#endif  /* NSPEAKER > 0 */
567/* spkr.c ends here */
568