* Copyright (c) 1994 Søren Schmidt
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software withough specific prior written permission
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "machine/cpufunc.h"
#include "machine/pcaudioio.h"
#include "i386/isa/isa.h"
#include "i386/isa/isa_device.h"
#include "i386/isa/timerreg.h"
#define INTERRUPT_RATE 16000
static struct pca_status
{
char open
; /* device open */
char queries
; /* did others try opening */
unsigned char *buf
[2]; /* double buffering */
unsigned char *buffer
; /* current buffer ptr */
unsigned in_use
[2]; /* buffers fill */
unsigned index
; /* index in current buffer */
unsigned counter
; /* sample counter */
unsigned scale
; /* sample counter scale */
unsigned sample_rate
; /* sample rate */
unsigned processed
; /* samples processed */
unsigned volume
; /* volume for pc-speaker */
char encoding
; /* Ulaw, Alaw or linear */
char current
; /* current buffer */
unsigned char oldval
; /* old timer port value */
char timer_on
; /* is playback running */
static char buffer1
[BUF_SIZE
];
static char buffer2
[BUF_SIZE
];
static char volume_table
[256];
static int pca_sleep
= 0;
static int pca_initialized
= 0;
int pcaprobe(struct isa_device
*dvp
);
int pcaattach(struct isa_device
*dvp
);
int pcaclose(dev_t dev
, int flag
);
int pcaopen(dev_t dev
, int flag
);
int pcawrite(dev_t dev
, struct uio
*uio
, int flag
);
int pcaioctl(dev_t dev
, int cmd
, caddr_t data
, int flag
, struct proc
*p
);
struct isa_driver pcadriver
= {
pcaprobe
, pcaattach
, "pca",
inline void conv(const void *table
, void *buff
, unsigned long n
)
__asm__("1:\tmovb (%2), %3\n"
:"b" ((long)table
), "c" (n
), "D" ((long)buff
), "a" ((char)n
)
j
= ((i
-128)*volume
)/100;
volume_table
[i
] = (((255-(j
+ 128))/4)+1);
pca_status
.buf
[0] = (unsigned char *)&buffer1
[0];
pca_status
.buf
[1] = (unsigned char *)&buffer2
[0];
pca_status
.buffer
= pca_status
.buf
[0];
pca_status
.in_use
[0] = pca_status
.in_use
[1] = 0;
pca_status
.sample_rate
= SAMPLE_RATE
;
pca_status
.scale
= (pca_status
.sample_rate
<< 8) / INTERRUPT_RATE
;
pca_status
.encoding
= AUDIO_ENCODING_ULAW
;
pca_volume(pca_status
.volume
);
/* use the first buffer */
pca_status
.buffer
= pca_status
.buf
[pca_status
.current
];
pca_status
.oldval
= inb(IO_PPI
) | 0x03;
if (acquire_timer2(TIMER_LSB
|TIMER_ONESHOT
)) {
if (acquire_timer0(INTERRUPT_RATE
, pcaintr
)) {
pca_status
.in_use
[0] = pca_status
.in_use
[1] = 0;
pca_status
.buffer
= pca_status
.buf
[pca_status
.current
];
pca_status
.oldval
= inb(IO_PPI
) | 0x03;
acquire_timer2(TIMER_LSB
|TIMER_ONESHOT
);
acquire_timer0(INTERRUPT_RATE
, pcaintr
);
while (pca_status
.in_use
[0] || pca_status
.in_use
[1]) {
tsleep((caddr_t
)&pca_sleep
, PZERO
|PCATCH
, "pca_drain", 0);
pcaprobe(struct isa_device
*dvp
)
pcaattach(struct isa_device
*dvp
)
printf(" PCM audio driver\n", dvp
->id_unit
);
pcaopen(dev_t dev
, int flag
)
/* audioctl device can always be opened */
/* audio device can only be open by one process */
pca_status
.buffer
= pca_status
.buf
[0];
pca_status
.in_use
[0] = pca_status
.in_use
[1] = 0;
pca_status
.processed
= 0;
pcaclose(dev_t dev
, int flag
)
/* audioctl device can always be closed */
/* audio device close drains all output and restores timers */
pcawrite(dev_t dev
, struct uio
*uio
, int flag
)
/* only audio device can be written */
while ((count
= min(BUF_SIZE
, uio
->uio_resid
)) > 0) {
which
= pca_status
.in_use
[0] ? 1 : 0;
if (count
&& !pca_status
.in_use
[which
]) {
uiomove(pca_status
.buf
[which
], count
, uio
);
pca_status
.processed
+= count
;
switch (pca_status
.encoding
) {
case AUDIO_ENCODING_ULAW
:
conv(ulaw_dsp
, pca_status
.buf
[which
], count
);
case AUDIO_ENCODING_ALAW
:
pca_status
.in_use
[which
] = count
;
if (!pca_status
.timer_on
)
if (pca_status
.in_use
[0] && pca_status
.in_use
[1]) {
tsleep((caddr_t
)&pca_sleep
, PZERO
|PCATCH
, "pca_wait",0);
pcaioctl(dev_t dev
, int cmd
, caddr_t data
, int flag
, struct proc
*p
)
auptr
= (audio_info_t
*)data
;
auptr
->play
.sample_rate
= pca_status
.sample_rate
;
auptr
->play
.channels
= 1;
auptr
->play
.precision
= 8;
auptr
->play
.encoding
= pca_status
.encoding
;
auptr
->play
.gain
= pca_status
.volume
;
auptr
->play
.samples
= pca_status
.processed
;
auptr
->play
.pause
= !pca_status
.timer_on
;
auptr
->play
.waiting
= pca_status
.queries
;
auptr
->play
.open
= pca_status
.open
;
auptr
->play
.active
= pca_status
.timer_on
;
auptr
= (audio_info_t
*)data
;
if (auptr
->play
.sample_rate
!= (unsigned int)~0) {
pca_status
.sample_rate
= auptr
->play
.sample_rate
;
(pca_status
.sample_rate
<< 8) / INTERRUPT_RATE
;
if (auptr
->play
.encoding
!= (unsigned int)~0) {
pca_status
.encoding
= auptr
->play
.encoding
;
if (auptr
->play
.gain
!= (unsigned int)~0) {
pca_status
.volume
= auptr
->play
.gain
;
pca_volume(pca_status
.volume
);
if (auptr
->play
.pause
!= (unsigned char)~0) {
if (pca_status
.index
< pca_status
.in_use
[pca_status
.current
]) {
__asm__("outb %0,$0x61\n"
: : "a" ((char)pca_status
.oldval
) );
: : "a" ((char)pca_status
.buffer
[pca_status
.index
]),
"b" ((long)volume_table
) );
outb(IO_PPI
, pca_status
.oldval
);
outb(IO_PPI
, pca_status
.oldval
& 0xFE);
volume_table
[pca_status
.buffer
[pca_status
.index
]]);
pca_status
.counter
+= pca_status
.scale
;
pca_status
.index
= (pca_status
.counter
>> 8);
pca_status
.index
= pca_status
.counter
= 0;
pca_status
.in_use
[pca_status
.current
] = 0;
pca_status
.buffer
= pca_status
.buf
[pca_status
.current
];
wakeup((caddr_t
)&pca_sleep
);