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