root/src/add-ons/kernel/drivers/audio/cmedia/pcm.c
/*
        Copyright 1999, Be Incorporated.   All Rights Reserved.
        This file may be used under the terms of the Be Sample Code License.
*/
#include <string.h>

#include <ByteOrder.h>
#include <MediaDefs.h>

#include "cm_private.h"
#include "sound.h"

#include <KernelExport.h>

extern int sprintf(char *, const char *, ...);


extern void dump_card(cmedia_pci_dev * card);

// Buffer header for audio server from BeOS R3 MediaDefs.h

typedef struct audio_buffer_header {
        int32           buffer_number;
        int32           subscriber_count;
        bigtime_t       time;
        int32           reserved_1;
        int32           reserved_2;
        bigtime_t       sample_clock;
} audio_buffer_header;

#if !defined(OLDAPI)
 #if DEBUG
  #define OLDAPI(x) dprintf x
 #else
  #define OLDAPI(x)
 #endif
#endif

#if DEBUG
int32 int_cnt;
int32 put_cnt;
bigtime_t the_time;
#endif

#if 0
/* early Intel kernels forgot to export these functions */
#undef B_HOST_TO_LENDIAN_FLOAT
#undef B_HOST_TO_BENDIAN_FLOAT
#undef B_LENDIAN_TO_HOST_FLOAT
#undef B_BENDIAN_TO_HOST_FLOAT

static float
_swap_float_(float x)
{
        uint32 temp1 = *(uint32*)&x;
        uint32 temp2 = ((temp1>>24)|((temp1>>8)&0xff00)|((temp1<<8)&0xff0000)|
                                        (temp1<<24));
        return *(float *)&temp2;
}

#if B_HOST_IS_BENDIAN
#define B_HOST_TO_LENDIAN_FLOAT(x) _swap_float_(x)
#define B_HOST_TO_BENDIAN_FLOAT(x) ((float)(x))
#define B_LENDIAN_TO_HOST_FLOAT(x) _swap_float_(x)
#define B_BENDIAN_TO_HOST_FLOAT(x) ((float)(x))
#else
#define B_HOST_TO_LENDIAN_FLOAT(x) ((float)(x))
#define B_HOST_TO_BENDIAN_FLOAT(x) _swap_float_(x)
#define B_LENDIAN_TO_HOST_FLOAT(x) ((float)(x))
#define B_BENDIAN_TO_HOST_FLOAT(x) _swap_float_(x)
#endif

#endif


static status_t pcm_open(const char *name, uint32 flags, void **cookie);
static status_t pcm_close(void *cookie);
static status_t pcm_free(void *cookie);
static status_t pcm_control(void *cookie, uint32 op, void *data, size_t len);
static status_t pcm_read(void *cookie, off_t pos, void *data, size_t *len);
static status_t pcm_write(void *cookie, off_t pos, const void *data, size_t *len);
//static status_t pcm_writev(void *cookie, off_t pos, const iovec *vec, size_t count, size_t *len); /* */

device_hooks pcm_hooks = {
    &pcm_open,
    &pcm_close,
    &pcm_free,
    &pcm_control,
    &pcm_read,
    &pcm_write,
    NULL,                       /* select */
    NULL,                       /* deselect */
    NULL,                       /* readv */
        NULL    // &pcm_writev          /* writev */
};

static pcm_cfg default_pcm = {
        44100.0,        /* sample rate */
        2,                      /* channels */
        0x2,            /* format */
#if B_HOST_IS_BENDIAN
        1,                      /* endian (big) */
#else
        0,                      /* endian (little) */
#endif
        0,                      /* header size */
        PLAYBACK_BUF_SIZE,      /* these are currently hard-coded */
        RECORD_BUF_SIZE         /* and cannot be changed without re-compile */
};


#if 0

typedef struct {
        uint8 control;
        uint8 imask;
        uint8 regs[0x2e];
} chip_state;

static void
save_state(
        pcm_dev * port,
        chip_state * state)
{
        int ix;
        state->control = get_direct(port->card, 0);
        state->imask = get_direct(port->card, 1);
        for (ix=0; ix<0x0e; ix++) {
                if (ix != 0x28 && ix != 0x29 && ix != 0x1a && ix != 0x1b) {
                        state->regs[ix] = get_indirect(port->card, ix+0x30);
                }
        }
}


static void
restore_state(
        pcm_dev * port,
        const chip_state * state)
{
        int ix;
        set_direct(port->card, 0, state->control, 0xff);
        for (ix=0; ix<0x0e; ix++) {
                if (ix != 0x28 && ix != 0x29 && ix != 0x1a && ix != 0x1b) {
                        set_indirect(port->card, ix, state->regs[ix]+0x30, 0xff);
                }
        }
        set_direct(port->card, 1, state->imask, 0xff);
}


static void
reset_chip(
        pcm_dev * port)
{
        set_direct(port->card, 0x1b, 0x40, 0x40);
        snooze(2);
        set_direct(port->card, 0x1b, 0x00, 0x40);
}

#endif


static void
stop_dma(
        pcm_dev * port)
{
        set_direct(port->card, 0x24, 0x40, 0x40);       // mute wave stream
        set_direct(port->card, 0x02, 0, 0x03);          // stop both ch0 and ch1

        set_direct(port->card, 0x02, 0x0c, 0x0c);       // reset both ch0 and ch1
        set_direct(port->card, 0x02, 0, 0x0c);
        ddprintf(("cmedia_pci: DMA stopped\n"));
}


static void
start_dma(
        pcm_dev * port)
{
        int     sample_size = 1;

        /* start out with a clean slate */

        KTRACE();
        ddprintf(("cmedia_pci: start_dma()\n"));
        if (port->config.format == 0x11) {
                memset((void*)port->card->low_mem, 0x80, port->config.play_buf_size +
                        port->config.rec_buf_size);
        }
        else {
                memset((void *)port->card->low_mem, 0, port->config.play_buf_size +
                        port->config.rec_buf_size);
        }

        port->wr_cur = port->wr_2;
        port->wr_silence = port->config.play_buf_size;
        port->was_written = 0;
        port->rd_cur = port->rd_2;
        port->was_read = port->config.rec_buf_size/2; /* how much has been read */
        port->wr_total = 0;
        port->rd_total = 0;

        /* we split the available low memory buffer in a small chunk */
        /* for playback, and a large chunk for recording. Then we split */
        /* each of those in half for double-buffering. While the DMA */
        /* just runs the entire range of the buffer, wrapping around when */
        /* done, the count register is set up to generate interrupt after */
        /* each half of the buffer. Because of latency requirements, we */
        /* will get 187 interrupts a second from playback, and 94 interrupts */
        /* a second from recording, at 48 kHz sampling rate, when buffers */
        /* are 2048 for playback and 4096 for record. */

        ddprintf(("play_buf_size %lx   rec_buf_size %lx\n",
                port->config.play_buf_size/2, port->config.rec_buf_size/2));

        PCI_IO_WR(port->dma_c, ((uint32)port->card->low_phys+
                port->config.play_buf_size)&0xff);
        PCI_IO_WR(port->dma_c+1, (((uint32)port->card->low_phys+
                port->config.play_buf_size)>>8)&0xff);
        PCI_IO_WR(port->dma_c+2, (((uint32)port->card->low_phys+
                port->config.play_buf_size)>>16)&0xff);
        PCI_IO_WR(port->dma_c+3, 0);
        /* if this is a 16 bit channel, divide transfer count in 2 */
        if (port->config.format != 0x11)
                sample_size *= 2;
        /* if this is a stereo channel, divide transfer count in 2 */
        if (port->config.channels == 2)
                sample_size *= 2;
        PCI_IO_WR(port->dma_c+4, (port->config.rec_buf_size/sample_size-1)&0xff);
        PCI_IO_WR(port->dma_c+5, ((port->config.rec_buf_size/sample_size-1)>>8)&0xff);
        PCI_IO_WR(port->dma_c+6, (port->rd_size/sample_size-1)&0xff);
        PCI_IO_WR(port->dma_c+7, ((port->rd_size/sample_size-1)>>8)&0xff);

        PCI_IO_WR(port->dma_a, ((uint32)port->card->low_phys)&0xff);
        PCI_IO_WR(port->dma_a+1, ((uint32)port->card->low_phys>>8)&0xff);
        PCI_IO_WR(port->dma_a+2, ((uint32)port->card->low_phys>>16)&0xff);
        PCI_IO_WR(port->dma_a+3, 0);
        PCI_IO_WR(port->dma_a+4, (port->config.play_buf_size/sample_size-1)&0xff);
        PCI_IO_WR(port->dma_a+5, ((port->config.play_buf_size/sample_size-1)>>8)&0xff);
        PCI_IO_WR(port->dma_a+6, (port->wr_size/sample_size-1)&0xff);
        PCI_IO_WR(port->dma_a+7, ((port->wr_size/sample_size-1)>>8)&0xff);

/* here, we should mute the PCM output to avoid clicking */

        ddprintf(("cmedia_pci: DMA starts as %lx/%lx\n", port->config.format, port->open_mode));
        set_direct(port->card, 0x24, 0x00, 0x40);

/* enable ch0 as play, and ch1 as record */
        set_direct(port->card, 0, 0x02, 0x03);
        set_direct(port->card, 0x02, 0x03, 0x03);

/* here, we should snooze for 16 samples' time, then un-mute the PCM output */
        KTRACE();
}


static status_t
configure_pcm(
        pcm_dev * port,
        pcm_cfg * config,
        bool force)
{
        status_t err = B_OK;
        int m = 0, n = 0, r = 0;        /* parameters for the PLL sample rate synthesizer */
        int asr = -1;   /* alternate sample rate divisor */
        uint32 s;       /* size of buffer */

        ddprintf(("cmedia_pci: configure_pcm()\n"));

        /* check args */
        if (config->sample_rate < 4000.0) {
                config->sample_rate = default_pcm.sample_rate;
        }
        if (config->sample_rate > 48000.0) {
                config->sample_rate = 48000.0;
        }
        if (config->channels < 1) {
                config->channels = default_pcm.channels;
        }
        if (config->channels > 2) {
                config->channels = default_pcm.channels;
        }
        /* secret format of format: upper nybble = signed, unsigned, float */
        /* lower nybble = bytes per sample */
        if ((config->format != 0x11) && (config->format != 0x2) &&
                (config->format != 0x24) && (config->format != 0x4)) {
                config->format = default_pcm.format;
        }
        if (config->buf_header < 0) {
                config->buf_header = 0;
        }

        /* figure out buffer size that's a power of two and within size limits */
        if (!config->play_buf_size) {
                /* default is 256 samples for a comfy 6 ms latency */
                s = 256*config->channels*(config->format&0xf);
        }       /* minimum is 32 samples for a more extreme 0.75ms latency */
        else for (s = 32*config->channels*(config->format&0xf); s < MIN_MEMORY_SIZE/2; s = (s<<1)) {
                if (s >= config->play_buf_size) {
                        break;
                }
        }
        config->play_buf_size = s;
        if (!config->rec_buf_size) {
                s = 256*config->channels*(config->format&0xf);
        }
        else for (s = 32*config->channels*(config->format&0xf); s < MIN_MEMORY_SIZE/2; s = (s<<1)) {
                if (s >= config->rec_buf_size) {
                        break;
                }
        }
        config->rec_buf_size = s;

        /* calculate m, n and r (and asr) */

        if (!force && abs(config->sample_rate - port->config.sample_rate) < config->sample_rate/250) {
                n = -1;
        }
        else if (config->sample_rate == 48000.0) {
                asr = 7;
        }
        else if (config->sample_rate == 32000.0) {
                asr = 6;
        }
        else if (config->sample_rate == 16000.0) {
                asr = 5;
        }
        else if (config->sample_rate == 8000.0) {
                asr = 4;
        }
        else if (config->sample_rate == 44100.0) {
                asr = 3;
        }
        else if (config->sample_rate == 22050.0) {
                asr = 2;
        }
        else if (config->sample_rate == 11025.0) {
                asr = 1;
        }
        else {
                float freq;
                float delta = 1000000.0;
                int sr = -1;
                int sn = -1;
                int sm = -1;
                float diff;

                for (r=0; r<8; r++) {
                        if ((1<<r)*config->sample_rate*512 < MIN_FREQ) {
                                continue;
                        }
                        break;
                }
                if (r == 8) {
                        OLDAPI(("cmedia_pci: r value is 8!\n"));
                        r = 7;
                }
                n = 0;
                do {
                        n++;
                        m = config->sample_rate*512/1000*(n+2)*(1<<r)/(F_REF/1000)-2;
                        if (m < 1) {
                                continue;
                        }
                        if (m > 255) {
                                ddprintf(("cmedia_pci: m > 255; outahere\n"));
                                break;
                        }
                        freq = (m+2)*(F_REF/1000)/(512*(n+2)*(1<<r)/1000);
                        diff = freq-config->sample_rate;
                        if (diff < 0) {
                                diff = -diff;
                        }
                        if (diff < delta) {
                                sr = r;
                                sn = n;
                                sm = m;
                        }
                } while (n < 31);
                r = sr;
                n = sn;
                m = sm;
                ddprintf(("cmedia_pci: m = %d   r = %d   n = %d\n", m, r, n));
        }

        /* configure device */

        if (!force) {
                stop_dma(port);
                /* should mute PCM out, too */
        }
        if (asr > -1 || n > -1) { /* new sampling rate */
                if (asr > -1) {
                        port->config.sample_rate = config->sample_rate;
                        set_direct(port->card, 0x05, (asr<<5)|(asr<<2), 0xfc);
                }
                else {
                        port->config.sample_rate = ((float)m+2.0)*(F_REF/1000.0)/
                                (0.512*(n+2.0)*(1<<r));
                        config->sample_rate = port->config.sample_rate;
#if 1
                        /* not exact the frequency supported */
#else
                        set_indirect(port->card, 0x24, m, 0xff);
                        set_indirect(port->card, 0x25, (r<<5)|n, 0xff);
                        set_indirect(port->card, 0x22, 0x00, 0xff);
#endif
                }
        }
        if (force || config->channels != port->config.channels ||
                config->format != port->config.format) {
                uchar val = 0;
                if (config->channels == 2) {
                        val |= 0x01;    /* stereo */
                }
                if (config->format != 0x11) {
                        val |= 0x02;    /* 16 bits */
                }
                set_direct(port->card, 0x08, (val<<2)|val, 0x0f); /* MCE -- may take time to take effect */
                port->config.channels = config->channels;
                port->config.format = config->format;
        }
        if (force || config->big_endian != port->config.big_endian) {
                port->config.big_endian = config->big_endian;
        }
        if (force || config->buf_header != port->config.buf_header) {
                port->config.buf_header = config->buf_header;
        }
        if (force || config->play_buf_size != port->config.play_buf_size*2) {
                port->config.play_buf_size = config->play_buf_size*2;   /* because we break it in two */
        }
        if (force || config->rec_buf_size != port->config.rec_buf_size*2) {
                port->config.rec_buf_size = config->rec_buf_size*2;     /* because we break it in two */
        }

/* here is where we should care about record and playback buffer sizes */

        ddprintf(("cmedia_pci: play %04lx rec %04lx\n", port->config.play_buf_size/2,
                port->config.rec_buf_size/2));

        port->wr_1 = port->card->low_mem;
        port->wr_2 = port->wr_1+port->config.play_buf_size/2;
        port->wr_size = port->config.play_buf_size/2;

        port->rd_1 = port->card->low_mem+port->config.play_buf_size;
        port->rd_2 = port->rd_1+port->config.rec_buf_size/2;
        port->rd_size = port->config.rec_buf_size/2;

        if (!force) {
                /* should un-mute PCM out, if we muted it */
                start_dma(port);
        }
        return err;
}


static status_t
pcm_open(
        const char * name,
        uint32 flags,
        void ** cookie)
{
        int ix;
        pcm_dev * port = NULL;
        char name_buf[256];
        int32 prev_mode;

        ddprintf(("cmedia_pci: pcm_open()\n"));

        *cookie = NULL;
        for (ix=0; ix<num_cards; ix++) {
                if (!strcmp(name, cards[ix].pcm.name)) {
                        goto gotit;
                }
        }
        for (ix=0; ix<num_cards; ix++) {
                if (!strcmp(name, cards[ix].pcm.oldname)) {
                        goto gotit;
                }
        }
        ddprintf(("cmedia_pci: %s not found\n", name));
        return ENODEV;

gotit:
        *cookie = port = &cards[ix].pcm;

        acquire_sem(port->init_sem);

        prev_mode = port->open_mode;
        if ((flags & 3) == O_RDONLY) {
                atomic_or(&port->open_mode, kRecord);
        }
        else if ((flags & 3) == O_WRONLY) {
                atomic_or(&port->open_mode, kPlayback);
        }
        else {
                atomic_or(&port->open_mode, kPlayback|kRecord);
        }

        if (atomic_add(&port->open_count, 1) == 0) {

                /* initialize device first time */

                port->card = &cards[ix];
                port->config = default_pcm;
                port->config.play_buf_size *= 2;
                port->config.rec_buf_size *= 2;

                /* playback */
                B_INITIALIZE_SPINLOCK(&port->wr_lock);
                port->dma_a = cards[ix].dma_base;
                port->wr_1 = cards[ix].low_mem;
                port->wr_2 = cards[ix].low_mem+port->config.play_buf_size/2;
                port->wr_size = port->config.play_buf_size/2;
                port->write_waiting = 0;
                sprintf(name_buf, "WS:%s", port->name);
                name_buf[B_OS_NAME_LENGTH-1] = 0;
                port->write_sem = create_sem(0, name_buf);
                if (port->write_sem < B_OK) {
                        port->open_count = 0;
                        return port->write_sem;
                }
                set_sem_owner(port->write_sem, B_SYSTEM_TEAM);
                name_buf[0] = 'W'; name_buf[1] = 'E';
                port->wr_entry = create_sem(1, name_buf);
                if (port->wr_entry < B_OK) {
                        delete_sem(port->write_sem);
                        port->open_count = 0;
                        return port->wr_entry;
                }
                set_sem_owner(port->wr_entry, B_SYSTEM_TEAM);
                name_buf[1] = 'T';
                port->wr_time_wait = 0;
                port->wr_time_sem = create_sem(0, name_buf);
                if (port->wr_time_sem < B_OK) {
                        delete_sem(port->write_sem);
                        delete_sem(port->wr_entry);
                        port->open_count = 0;
                        return port->wr_time_sem;
                }
                set_sem_owner(port->wr_time_sem, B_SYSTEM_TEAM);

                /* recording */

                B_INITIALIZE_SPINLOCK(&port->rd_lock);
                port->dma_c = cards[ix].dma_base+0x08;
                port->rd_1 = cards[ix].low_mem+port->config.play_buf_size;
                port->rd_2 = cards[ix].low_mem+port->config.play_buf_size+port->config.rec_buf_size/2;
                port->rd_size = port->config.rec_buf_size/2;
                port->read_waiting = 0;
                name_buf[0] = 'R'; name_buf[1] = 'S';
                port->read_sem = create_sem(0, name_buf);
                if (port->read_sem < B_OK) {
                        delete_sem(port->write_sem);
                        delete_sem(port->wr_entry);
                        delete_sem(port->wr_time_sem);
                        port->open_count = 0;
                        return port->read_sem;
                }
                set_sem_owner(port->read_sem, B_SYSTEM_TEAM);
                name_buf[0] = 'R'; name_buf[1] = 'E';
                port->rd_entry = create_sem(1, name_buf);
                if (port->rd_entry < B_OK) {
                        delete_sem(port->write_sem);
                        delete_sem(port->wr_entry);
                        delete_sem(port->read_sem);
                        delete_sem(port->wr_time_sem);
                        port->open_count = 0;
                        return port->rd_entry;
                }
                set_sem_owner(port->rd_entry, B_SYSTEM_TEAM);
                name_buf[1] = 'T';
                port->rd_time_wait = 0;
                port->rd_time_sem = create_sem(0, name_buf);
                if (port->rd_time_sem < B_OK) {
                        delete_sem(port->write_sem);
                        delete_sem(port->wr_entry);
                        delete_sem(port->read_sem);
                        delete_sem(port->wr_time_sem);
                        delete_sem(port->rd_entry);
                        port->open_count = 0;
                        return port->rd_time_sem;
                }
                set_sem_owner(port->rd_time_sem, B_SYSTEM_TEAM);

                port->rd_time = 0;
                port->next_rd_time = 0;
                port->wr_time = 0;

                /* old API */

                port->old_cap_sem = -1;
                port->old_play_sem = -1;

                /* configuration */

                configure_pcm(port, &default_pcm, true);

                /* interrupts */
                KTRACE();
                increment_interrupt_handler(port->card);

                set_direct(port->card, 0x0e, 0x03, 0x03);       /* */
                start_dma(port);

                /* initialization is done, let other clients of the driver go */
        } else {
                if (prev_mode != port->open_mode) {
                        pcm_cfg temp = port->config;
                        temp.play_buf_size /= 2;
                        temp.rec_buf_size /= 2;
                        configure_pcm(port, &temp, false);      /* change rec/play if needed */
                }
        }
        release_sem(port->init_sem);

#if DEBUG
        dump_card(&cards[ix]);
#endif

        return B_OK;
}


static status_t
pcm_close(
        void * cookie)
{
        pcm_dev * port = (pcm_dev *)cookie;
        cpu_status cp;
        int spin = 0;

        ddprintf(("cmedia_pci: pcm_close()\n"));

        acquire_sem(port->init_sem);

        if (atomic_add(&port->open_count, -1) == 1) {

                KTRACE();
                cp = disable_interrupts();
                acquire_spinlock(&port->card->hardware);

                /* turn off interrupts */
                stop_dma(port);
                set_direct(port->card, 0x0e, 0x00, 0x03);       /* */

                if (port->config.format == 0x11) {
                        memset((void *)port->wr_1, 0x80, port->config.play_buf_size);   /* play silence */
                }
                else {
                        memset((void *)port->wr_1, 0, port->config.play_buf_size);      /* play silence */
                }
                spin = 1;

                release_spinlock(&port->card->hardware);
                restore_interrupts(cp);

                delete_sem(port->write_sem);
                delete_sem(port->read_sem);
                delete_sem(port->wr_entry);
                delete_sem(port->rd_entry);
                delete_sem(port->rd_time_sem);
                delete_sem(port->wr_time_sem);
                port->write_sem = -1;
                port->read_sem = -1;
                port->wr_entry = -1;
                port->rd_entry = -1;
                port->rd_time_sem = -1;
                port->wr_time_sem = -1;
        }
        release_sem(port->init_sem);

        if (spin) {
                /* wait so we know FIFO gets filled with silence */
                snooze(port->config.play_buf_size*1000/(port->config.sample_rate*
                        (port->config.format&0xf)*port->config.channels/1000));
        }
        return B_OK;
}


static status_t
pcm_free(
        void * cookie)
{
        cpu_status cp;
        pcm_dev * port = (pcm_dev *)cookie;

        ddprintf(("cmedia_pci: pcm_free()\n"));

        acquire_sem(port->init_sem);

        if (((pcm_dev *)cookie)->open_count == 0) {

                /* the last free will actually stop everything  */

                KTRACE();
                cp = disable_interrupts();
                acquire_spinlock(&port->card->hardware);

                decrement_interrupt_handler(port->card);

                release_spinlock(&port->card->hardware);
                restore_interrupts(cp);
        }
        release_sem(port->init_sem);

        return B_OK;
}


static status_t
pcm_control(
        void * cookie,
        uint32 iop,
        void * data,
        size_t len)
{
        // declarations for SPDIF settings I/O
        uchar reg_value;
        char DriverVersion[] = "1.3.2 (Jul 17, 2001)";

        pcm_dev * port = (pcm_dev *)cookie;
        status_t err = B_BAD_VALUE;
        pcm_cfg config = port->config;
        static float rates[7] = { 48000.0, 44100.0, 32000.0, 22050.0, 16000.0, 11025.0, 8000.0 };
        bool configure = false;
        config.play_buf_size /= 2;
        config.rec_buf_size /= 2;

        ddprintf(("cmedia_pci: pcm_control()\n"));

        switch (iop) {
        case B_AUDIO_GET_AUDIO_FORMAT:
                memcpy(data, &config, sizeof(port->config));
                err = B_OK;
                break;
        case B_AUDIO_GET_PREFERRED_SAMPLE_RATES:
                memcpy(data, rates, sizeof(rates));
                err = B_OK;
                break;
        case B_AUDIO_SET_AUDIO_FORMAT:
                memcpy(&config, data, sizeof(config));
                configure = true;
                err = B_OK;
                break;
        case SV_RD_TIME_WAIT:
                atomic_add(&port->rd_time_wait, 1);
                err = acquire_sem(port->rd_time_sem);
                if (err >= B_OK) {
                        cpu_status cp;
                        KTRACE();
                        cp = disable_interrupts();
                        acquire_spinlock(&port->rd_lock);
                        ((sv_timing *)data)->time = port->rd_time;
                        ((sv_timing *)data)->bytes = port->rd_total;
                        ((sv_timing *)data)->skipped = port->rd_skipped;
                        ((sv_timing *)data)->_reserved_[0] = 0xffffffffUL;
                        release_spinlock(&port->rd_lock);
                        restore_interrupts(cp);
                }
                break;
        case SV_WR_TIME_WAIT:
                atomic_add(&port->wr_time_wait, 1);
                err = acquire_sem(port->wr_time_sem);
                if (err >= B_OK) {
                        cpu_status cp;
                        KTRACE();
                        cp = disable_interrupts();
                        acquire_spinlock(&port->wr_lock);
                        ((sv_timing *)data)->time = port->wr_time;
                        ((sv_timing *)data)->bytes = port->wr_total;
                        ((sv_timing *)data)->skipped = port->wr_skipped;
                        ((sv_timing *)data)->_reserved_[0] = 0xffffffffUL;
                        release_spinlock(&port->wr_lock);
                        restore_interrupts(cp);
                }
                break;
        case SV_SECRET_HANDSHAKE: {
                cpu_status cp;
                KTRACE();
                cp = disable_interrupts();
                acquire_spinlock(&port->wr_lock);
                acquire_spinlock(&port->rd_lock);
                ((sv_handshake *)data)->wr_time = port->wr_time;
                ((sv_handshake *)data)->wr_skipped = port->wr_skipped;
                ((sv_handshake *)data)->rd_time = port->rd_time;
                ((sv_handshake *)data)->rd_skipped = port->rd_skipped;
                ((sv_handshake *)data)->wr_total = port->wr_total;
                ((sv_handshake *)data)->rd_total = port->rd_total;
                ((sv_handshake *)data)->_reserved_[0] = 0xffffffffUL;
                err = B_OK;
                release_spinlock(&port->rd_lock);
                release_spinlock(&port->wr_lock);
                restore_interrupts(cp);
                } break;
        case SOUND_GET_PARAMS: {
                cpu_status cp;
                uchar u;
                sound_setup * sound = (sound_setup *)data;
                err = B_OK;
                cp = disable_interrupts();
                acquire_spinlock(&port->card->hardware);
                /* Here we get to hard-code the mix/mux values. */
                /* Huh-huh; he said "hard-code"! */
                sound->sample_rate = kHz_44_1;
                if (!port->config.big_endian == !B_HOST_IS_BENDIAN) {
                        sound->playback_format = linear_16bit_big_endian_stereo;
                        sound->capture_format = linear_16bit_big_endian_stereo;
                }
                else {
                        sound->playback_format = linear_16bit_little_endian_stereo;
                        sound->capture_format = linear_16bit_little_endian_stereo;
                }
                sound->dither_enable = false;
                sound->loop_attn = 0;
                sound->loop_enable = 0;
                sound->output_boost = 0;
                sound->highpass_enable = 0;
                /* this is a master control on C-Media... */
                u = get_indirect(port->card, 0x30)>>2;
                sound->mono_gain = u&63;
                sound->mono_mute = 0;

                /* left channel */
                u = get_indirect(port->card, 0x3d); // Legacy SB compatible Mixer
                switch (u)
                {
                case 0x10:
                        sound->left.adc_source = line;          //      record line left
                        break;

                case 4:
                        sound->left.adc_source = aux1;          // record CD left ??
                        break;

                case 1:
                        sound->left.adc_source = mic;           // record mic left
                        break;

                default:
                        sound->left.adc_source = loopback;
                        break;
                }
                u = get_indirect(port->card, 0x3f)>>4;
                sound->left.adc_gain = u&15;

                u = get_direct(port->card, 0x25)<<4;
                sound->left.mic_gain_enable = u&16;

                u = get_indirect(port->card, 0x36)>>3;
                sound->left.aux1_mix_gain = 31-(u&31);

                u = get_indirect(port->card, 0x3c)<<5;
                sound->left.aux1_mix_mute = ~u&128;

                u = get_indirect(port->card, 0x34)>>3;
                sound->left.aux2_mix_gain = 31-(u&31);

                u = get_direct(port->card, 0x24);
                sound->left.aux2_mix_mute = u&128;

                u = get_indirect(port->card, 0x38)>>3;
                sound->left.line_mix_gain = 31-(u&31);

                u = get_indirect(port->card, 0x3c)<<3;
                sound->left.line_mix_mute = ~u&128;

                u = get_indirect(port->card, 0x32)>>2;
                sound->left.dac_attn = 63-(u&63);

                u = get_direct(port->card, 0x24)<<1;
                sound->left.dac_mute = u&128;

                /* right channel */
                u = get_indirect(port->card, 0x3e);
                switch (u)
                {
                case 8:
                        sound->right.adc_source = line;         //record line right
                        break;

                case 2:
                        sound->right.adc_source = aux1;         // record CD right?
                        break;

                case 1:
                        sound->right.adc_source = mic;          // record mic right
                        break;

                default:
                        sound->right.adc_source = loopback;
                        break;
                }
                u = get_indirect(port->card, 0x40)>>4;
                sound->right.adc_gain = u&15;
                sound->right.mic_gain_enable = sound->left.mic_gain_enable;
                u = get_indirect(port->card, 0x37)>>3;
                sound->right.aux1_mix_gain = 31-(u&31);
                u = get_indirect(port->card, 0x3c)<<6;
                sound->right.aux1_mix_mute = ~u&128;
                u = get_indirect(port->card, 0x35)>>3;
                sound->right.aux2_mix_gain = 31-(u&31);
                u = get_direct(port->card, 0x24);
                sound->right.aux2_mix_mute = u&128;
                u = get_indirect(port->card, 0x39)>>3;
                sound->right.line_mix_gain = 31-(u&31);
                u = get_indirect(port->card, 0x3c)<<4;
                sound->right.line_mix_mute = ~u&128;
                u = get_indirect(port->card, 0x33)>>2;
                sound->right.dac_attn = 63-(u&63);
                u = get_direct(port->card, 0x24)<<1;
                sound->right.dac_mute = u&128;
                /* done */
                release_spinlock(&port->card->hardware);
                restore_interrupts(cp);
                } break;
        case SOUND_SET_PARAMS: {
                cpu_status cp;
                uchar u;
                sound_setup * sound = (sound_setup *)data;
                err = B_OK;
                cp = disable_interrupts();
                acquire_spinlock(&port->card->hardware);
                /* Here we get to hard-code the mix/mux values. */
                /* Huh-huh; he said "hard-code"! */

                /* ignore sample rate */
                sound->sample_rate = kHz_44_1;
                if (config.sample_rate < 43999 || config.sample_rate > 44201) {
                        config.sample_rate = 44100.0;
                        configure = true;
                }
                /* we only support 16-bit formats */
                if (sound->playback_format == linear_16bit_big_endian_stereo &&
                        sound->capture_format == linear_16bit_big_endian_stereo) {
                        if (!config.big_endian != !B_HOST_IS_BENDIAN || config.format != 0x2) {
                                config.big_endian = B_HOST_IS_BENDIAN;
                                config.format = 0x2;
                                configure = true;
                        }
                        OLDAPI(("same_endian\n"));
                }
                else if (sound->playback_format == linear_16bit_little_endian_stereo &&
                        sound->capture_format == linear_16bit_little_endian_stereo) {
                        if (!config.big_endian != !!B_HOST_IS_BENDIAN || config.format != 0x2) {
                                config.big_endian = !B_HOST_IS_BENDIAN;
                                config.format = 0x2;
                                configure = true;
                        }
                        OLDAPI(("other_endian\n"));
                }
                else {
                        config.big_endian = !!B_HOST_IS_BENDIAN;
                        configure = true;
                        OLDAPI(("other format!!!\n"));
                }
                /* ignore these values */
                sound->dither_enable = false;
                sound->loop_attn = 0;
                sound->loop_enable = 0;
                sound->output_boost = 0;
                sound->highpass_enable = 0;
                /* this is a stereo control on C-Media... */
                u = (sound->mono_gain>>1)&0x1f;
                OLDAPI(("output: %x\n", u));
                set_indirect(port->card, 0x30, u<<3, 0xff);
                set_indirect(port->card, 0x31, u<<3, 0xff);
                /* left channel */
                switch (sound->left.adc_source)
                {
                case line:
                        u = 1<<4;
                        break;
                case aux1:
                        u = 1<<2;
                        break;
                case mic:
                        u = 1<<0;
                        break;
                default:
                        u = 0x15;
                        break;
                }
                OLDAPI(("input: %x\n", u));
                set_indirect(port->card, 0x3d, u, 0xff);
                u = (sound->left.adc_gain&15);
                set_indirect(port->card, 0x3f, u<<4, 0xff);
                u = sound->left.mic_gain_enable ? 0 : 0x01;
                set_direct(port->card, 0x25, u, 0x01);
                u = 31-(sound->left.aux1_mix_gain&31);
                OLDAPI(("cd: %x\n", u));
                set_indirect(port->card, 0x36, u<<3, 0xff);
                u = sound->left.aux1_mix_mute ? 0 : 0x04;
                set_indirect(port->card, 0x3c, u, 0x04);
                u = 31-(sound->left.aux2_mix_gain&31);
                OLDAPI(("aux2: %x\n", u));
                set_indirect(port->card, 0x34, u<<3, 0xff);
                u = sound->left.aux2_mix_mute ? 0x80 : 0;
                set_direct(port->card, 0x24, u, 0x80);
                u = 31-(sound->left.line_mix_gain&31);
                OLDAPI(("line: %x\n", u));
                set_indirect(port->card, 0x38, u<<3, 0xff);
                u = sound->left.line_mix_mute ? 0 : 0x10;
                set_indirect(port->card, 0x3c, u, 0x10);
                u = 63-(sound->left.dac_attn & 63);
                OLDAPI(("PCM: %x\n", u));
                set_indirect(port->card, 0x32, u<<2, 0xff);
                u = sound->left.dac_mute ? 0x40 : 0;
                set_direct(port->card, 0x24, u, 0x40);
                /* right channel */
                switch (sound->right.adc_source) {
                case line:
                        u = 1<<3;
                        break;
                case aux1:
                        u = 1<<1;
                        break;
                case mic:
                        u = 1<<0;
                        break;
                default:
                        u = 0x0a;
                        break;
                }
                sound->right.mic_gain_enable = sound->left.mic_gain_enable;
                set_indirect(port->card, 0x3e, u, 0xff);
                u = (sound->right.adc_gain&15);
                set_indirect(port->card, 0x40, u<<4, 0xff);
                u = sound->right.mic_gain_enable ? 0 : 0x01;
                set_direct(port->card, 0x25, u, 0x01);
                u = 31-(sound->right.aux1_mix_gain&31);
                set_indirect(port->card, 0x37, u<<3, 0xff);
                u = sound->right.aux1_mix_mute ? 0 : 0x02;
                set_indirect(port->card, 0x3c, u, 0x02);
                u = 31-(sound->right.aux2_mix_gain&31);
                set_indirect(port->card, 0x35, u<<3, 0xff);
                u = sound->right.aux2_mix_mute ? 0x80 : 0;
                set_direct(port->card, 0x24, u, 0x80);
                u = 31-(sound->right.line_mix_gain&31);
                set_indirect(port->card, 0x39, u<<3, 0xff);
                u = sound->right.line_mix_mute ? 0 : 0x08;
                set_indirect(port->card, 0x3c, u, 0x08);
                u = 63-(sound->right.dac_attn & 63);
                set_indirect(port->card, 0x33, u<<2, 0xff);
                u = sound->right.dac_mute ? 0x40 : 0;
                set_direct(port->card, 0x24, u, 0x40);
                /* done */
                release_spinlock(&port->card->hardware);
                restore_interrupts(cp);
                } break;
        case SOUND_SET_PLAYBACK_COMPLETION_SEM:
                port->old_play_sem = *(sem_id *)data;
                err = B_OK;
                break;
        case SOUND_SET_CAPTURE_COMPLETION_SEM:
                port->old_cap_sem = *(sem_id *)data;
                err = B_OK;
                break;
//      case SOUND_GET_PLAYBACK_TIMESTAMP:
//              break;
//      case SOUND_GET_CAPTURE_TIMESTAMP:
//              break;
//      case SOUND_DEBUG_ON:
//              break;
//      case SOUND_DEBUG_OFF:
//              break;
        case SOUND_UNSAFE_WRITE: {
                audio_buffer_header * buf = (audio_buffer_header *)data;
                size_t n = buf->reserved_1-sizeof(*buf);
                pcm_write(cookie, 0, buf+1, &n);
                buf->time = port->wr_time;
                buf->sample_clock = port->wr_total/4 * 10000 / 441;
                err = release_sem(port->old_play_sem);
                } break;
        case SOUND_UNSAFE_READ: {
                audio_buffer_header * buf = (audio_buffer_header *)data;
                size_t n = buf->reserved_1-sizeof(*buf);
                pcm_read(cookie, 0, buf+1, &n);
                buf->time = port->rd_time;
                buf->sample_clock = port->rd_total/4 * 10000 / 441;
                err = release_sem(port->old_cap_sem);
                } break;
        case SOUND_LOCK_FOR_DMA:
                err = B_OK;
                break;
        case SOUND_SET_PLAYBACK_PREFERRED_BUF_SIZE:
                config.play_buf_size = (intptr_t)data;
                configure = true;
                err = B_OK;
                break;
        case SOUND_SET_CAPTURE_PREFERRED_BUF_SIZE:
                config.rec_buf_size = (intptr_t)data;
                configure = true;
                err = B_OK;
                break;
        case SOUND_GET_PLAYBACK_PREFERRED_BUF_SIZE:
                *(int32*)data = config.play_buf_size;
                err = B_OK;
                break;
        case SOUND_GET_CAPTURE_PREFERRED_BUF_SIZE:
                *(int32*)data = config.rec_buf_size;
                err = B_OK;
                break;


// control ports for SPDIF settings
        case SOUND_GET_SPDIF_IN_OUT_LOOPBACK:
                *(int8 *)data = 0;
                reg_value = get_direct( port->card, 0x04 );
                if( reg_value && 0x80 ) *(int8 *)data = 1;
                err = B_OK;
                break;

        case SOUND_SET_SPDIF_IN_OUT_LOOPBACK:
                if( *(int8 *)data == 0 ) // disable SPDIF-IN loopback to SPDIF (bypass)
                        set_direct( port->card, 0x04, 0x00, 0x80 );
                else // enable SPDIF-IN loopback to SPDIF (bypass)
                        set_direct( port->card, 0x04, 0x80, 0x80 );
                err = B_OK;
                break;




        case SOUND_GET_SPDIF_OUT:
                *(int8 *)data = 0;
                reg_value = get_direct( port->card, 0x16 );                     // Adresse 0x16
                if( reg_value && 0x80 ) *(int8 *)data = 1;
                err = B_OK;
                break;

        case SOUND_SET_SPDIF_OUT:
                if( *(int8 *)data == 0 ) // disable SPDIF-OUT
                        set_direct( port->card, 0x16, 0x00, 0x80);
                else // enable SPDIF-OUT
                        set_direct( port->card, 0x16, 0x80, 0x80 );
                err = B_OK;
                break;



        case SOUND_GET_SPDIF_MONITOR:
                *(int8 *)data = 0;
                reg_value = get_direct( port->card, 0x24 );
                if( reg_value && 0x01 ) *(int8 *)data = 1;
                err = B_OK;
                break;


        case SOUND_SET_SPDIF_MONITOR:
                if( *(int8 *)data == 0 ) // disable SPDIF_IN PCM to DAC (CDPlay)
                        set_direct( port->card, 0x24, 0x00, 0x01 );
                else // enable SPDIF_IN PCM to DAC (CDPlay)
                        set_direct( port->card, 0x24, 0x01, 0x01 );
                err = B_OK;
                break;

        case SOUND_GET_SPDIF_OUT_LEVEL:
                *(int8 *)data = 0;
                reg_value = get_direct( port->card, 0x1b );
                if( reg_value && 0x02 ) *(int8 *)data = 1;
                err = B_OK;
                break;

        case SOUND_SET_SPDIF_OUT_LEVEL:
                if( *(int8 *)data == 0 ) // enable SPDIF-OUT optical
                        set_direct( port->card, 0x1b, 0x00, 0x02 );
                else // enable SPDIF-OUT coaxial
                        set_direct( port->card, 0x1b, 0x02, 0x02 );
                break;

        case SOUND_GET_SPDIF_IN_FORMAT:
                *(int8 *)data = 0;
                reg_value = get_direct( port->card, 0x08 );             // Adresse 0x08
                if( reg_value && 0x80 ) *(int8 *)data = 1;
                err = B_OK;
                break;


        case SOUND_SET_SPDIF_IN_FORMAT:
                if( *(int8 *)data == 0 ) // disable SPDIF inverse (SPDIF normal)
                        set_direct( port->card, 0x08, 0x00, 0x80 );
                else // enable SPDIF inverse
                        set_direct( port->card, 0x08, 0x80, 0x80 );     // Adresse 0x08, Daten 0x80
                err = B_OK;
                break;


        case SOUND_GET_SPDIF_IN_OUT_COPYRIGHT:
                *(int8 *)data = 0;
                reg_value = get_direct( port->card, 0x16 );
                if( reg_value && 0x40 ) *(int8 *)data = 1;
                err = B_OK;
                break;

        case SOUND_SET_SPDIF_IN_OUT_COPYRIGHT:
                if( *(int8 *)data == 0 ) // disable SPDIF-IN/OUT copyright protection
                        set_direct( port->card, 0x16, 0x00, 0x40 );
                else // enable SPDIF-IN/OUT copyright protection
                        set_direct( port->card, 0x16, 0x40, 0x40 );
                err = B_OK;
                break;

        case SOUND_GET_SPDIF_IN_VALIDITY:
                *(int8 *)data = 0;
                reg_value = get_direct( port->card, 0x27 );
                if( reg_value && 0x02 ) *(int8 *)data = 1;
                err = B_OK;
                break;

        case SOUND_SET_SPDIF_IN_VALIDITY:
                if( *(int8 *)data == 0 ) // disable SPDIF-IN validity detection
                        set_direct( port->card, 0x27, 0x00, 0x02 );
                else // enable SPDIF-IN validity detection
                        set_direct( port->card, 0x27, 0x02, 0x02 );
                err = B_OK;
                break;
// control ports for analog settings

        case SOUND_GET_4_CHANNEL_DUPLICATE:
                *(int8 *)data = 0;
                reg_value = get_direct( port->card, 0x1b );
                if( reg_value && 0x04 ) *(int8 *)data = 1;

//              0x1b, 0x04, 0x04,       /* dual channel mode enable */
//              0x1a, 0x00, 0x80,       /* Double DAC structure disable */

                err = B_OK;
                break;

        case SOUND_SET_4_CHANNEL_DUPLICATE:
                if( *(int8 *)data == 0 ) // disable 4 channel analog duplicate mode
                        set_direct( port->card, 0x1b, 0x00, 0x04 );
                else // enable 4 channel analog duplicate mode
                        set_direct( port->card, 0x1b, 0x04, 0x04 );
                err = B_OK;
                break;
// control ports for additional info

        case SOUND_GET_DEVICE_ID:
//              *(int32*)data.vendor_id = cards[0].info.vendor_id;
//              *(int32*)data.device_id = cards[0].info.device_id;

//              static int32 chipinfo[] = { 0,0 };
//              chipinfo[0] = cards[0].info.vendor_id;
                *(int32*)data = cards[0].info.device_id;

//              memcpy(data, &chipinfo, sizeof(chipinfo));
                err = B_OK;
                break;

        case SOUND_GET_INTERNAL_CHIP_ID:
                // XXX
                break;

        case SOUND_GET_DRIVER_VERSION:
                memcpy(data, &DriverVersion, sizeof(DriverVersion));
                break;

        default:
                OLDAPI(("cmedia_pci: unknown code %ld\n", iop));
                err = B_BAD_VALUE;
                break;
        }
        if ((err == B_OK) && configure) {
                cpu_status cp;
                KTRACE();
                cp = disable_interrupts();
                acquire_spinlock(&port->card->hardware);
                err = configure_pcm(port, &config, false);
                release_spinlock(&port->card->hardware);
                restore_interrupts(cp);
        }
        return err;
}


static void
copy_short_to_float(
        float * f,
        const short * s,
        int c,
        int endian)     /*      endian means float data in big-endian   */
{
        if (endian) {
                while (c > 1) {
                        short sh = B_LENDIAN_TO_HOST_FLOAT(*s);
                        *(f++) = B_HOST_TO_BENDIAN_FLOAT((float)B_LENDIAN_TO_HOST_INT16(sh));
                        s++;
                        c -= 2;
                }
        }
        else {
                while (c > 1) {
                        short sh = B_LENDIAN_TO_HOST_FLOAT(*s);
                        *(f++) = B_HOST_TO_LENDIAN_FLOAT((float)B_LENDIAN_TO_HOST_INT16(sh));
                        s++;
                        c -= 2;
                }
        }
}


static void
copy_float_to_short(
        short * s,
        const float * f,
        int c,
        int endian)     /*      endian means float data in big-endian   */
{
        if (endian) {
                while (c > 1) {
                        float fl = *(f++);
                        *(s++) = B_HOST_TO_LENDIAN_INT16((float)B_BENDIAN_TO_HOST_FLOAT(fl));
                        c -= 2;
                }
        }
        else {
                while (c > 1) {
                        float fl = *(f++);
                        *(s++) = B_HOST_TO_LENDIAN_INT16((float)B_LENDIAN_TO_HOST_FLOAT(fl));
                        c -= 2;
                }
        }
}


static void
swap_copy(
        short * dest,
        const short * src,
        int c)
{
        while (c > 1) {
                unsigned short sh = *(src++);
                *(dest++) = ((sh << 8) | (sh >> 8));
                c -= 2;
        }
}


static status_t
pcm_read(
        void * cookie,
        off_t pos,
        void * data,
        size_t * nread)
{
        pcm_dev * port = (pcm_dev *)cookie;
        size_t to_read = *nread;
        status_t err;
        size_t block;
        cpu_status cp;
        int bytes_xferred;
        void * hdrptr = data;
        int hdrsize = port->config.buf_header;
        cmedia_pci_audio_buf_header hdr;

//      ddprintf(("cmedia_pci: pcm_read()\n")); /* we're here */

        *nread = 0;
        data = ((char *)data)+hdrsize;
        to_read -= hdrsize;

        err = acquire_sem_etc(port->rd_entry, 1, B_CAN_INTERRUPT, 0);
        if (err < B_OK) {
                return err;
        }

        hdr.capture_time = port->rd_time;

        goto first_time;

        while (to_read > 0) {
                /* wait for more data */
                atomic_add(&port->read_waiting, 1);
                err = acquire_sem_etc(port->read_sem, 1, B_CAN_INTERRUPT, 0);
                if (err < B_OK) {
                        release_sem(port->rd_entry);
                        return err;
                }

first_time:     /* we need to check whether anything's available first */
                KTRACE();
                cp = disable_interrupts();
                acquire_spinlock(&port->rd_lock);

                block = port->rd_size-port->was_read;

                if (port->config.format == 0x24) {
                        if (block > (to_read>>1)) {     /*      floats expand by factor 2       */
                                block = to_read>>1;
                        }
                }
                else if (block > to_read) {
                        block = to_read;
                }
                switch (port->config.format) {
                case 0x24:      /*      floats  */
                        copy_short_to_float((float *)data, (const short *)(port->rd_cur+port->was_read),
                                block, !B_HOST_IS_LENDIAN == !port->config.big_endian);
                        bytes_xferred = block * 2;
                        break;
                case 0x02:      /*      shorts  */
                        if (!B_HOST_IS_LENDIAN == !port->config.big_endian) {
                                /*      we need to swap */
                                swap_copy((short *)data, (const short *)(port->rd_cur+port->was_read), block);
                                bytes_xferred = block;
                                break;
                        }
                        /*      else fall through to default case       */
                case 0x11:      /*      bytes   */
                default:
                        memcpy(data, (void *)(port->rd_cur+port->was_read), block);
                        bytes_xferred = block;
                        break;
                }
                port->was_read += block;

                release_spinlock(&port->rd_lock);
                restore_interrupts(cp);

                to_read -= bytes_xferred;
                data = ((char *)data)+bytes_xferred;
                *nread += bytes_xferred;
        }

        /*      provide header if requested     */
        if (hdrsize > 0) {
                ddprintf(("header %d\n", hdrsize));
                *nread += hdrsize;
                hdr.capture_size = *nread;
                hdr.sample_rate = port->config.sample_rate;
                if ((uint32)hdrsize > sizeof(hdr))
                        hdrsize = sizeof(hdr);

                memcpy(hdrptr, &hdr, hdrsize);
        }

        release_sem(port->rd_entry);

        return B_OK;
}


static status_t
pcm_write(
        void * cookie,
        off_t pos,
        const void * data,
        size_t * nwritten)
{
        pcm_dev * port = (pcm_dev *)cookie;
        status_t err;
        cpu_status cp;
        int written = 0;
        int to_write = *nwritten;       /*       in play bytes, not input bytes!        */
        int block;
        int bytes_xferred;

//      ddprintf(("cmedia_pci: pcm_write()\n")); /* we're here */

        *nwritten = 0;

        err = acquire_sem_etc(port->wr_entry, 1, B_CAN_INTERRUPT, 0);
        if (err < B_OK) {
                return err;
        }

        atomic_add(&port->write_waiting, 1);
        if (port->config.format == 0x24) {
                to_write >>= 1; /*      floats collapse by 2    */
        }
        while (to_write > 0) {

                /* wait to write */

                err = acquire_sem_etc(port->write_sem, 1, B_CAN_INTERRUPT, 0);
                if (err < B_OK) {
                        release_sem(port->wr_entry);
                        return err;
                }

#if DEBUG
                put_cnt++;
                {
                        bigtime_t delta = system_time() - the_time;
                        if (delta < 1) {
                                ddprintf(("cmedia_pci: delta %lld (low!) #%ld\n", delta, put_cnt));
                        }
                        else if (delta > 2000) {
                                ddprintf(("cmedia_pci: delta %lld (high!) #%ld\n", delta, put_cnt));
                        }
                }
                if (put_cnt != int_cnt) {
        static int last;
                        if (last != int_cnt-put_cnt)
                                OLDAPI(("cmedia_pci: %ld mismatch\n", int_cnt-put_cnt));
                        last = int_cnt-put_cnt;
                }
#endif /* DEBUG */

                KTRACE();
                cp = disable_interrupts();
                acquire_spinlock(&port->wr_lock);

                block = port->wr_size-port->was_written;
                if (block > to_write) {
                        /* must let next guy in */
                        if (atomic_add(&port->write_waiting, -1) > 0) {
                                release_sem_etc(port->write_sem, 1, B_DO_NOT_RESCHEDULE);
                        }
                        else {
                                atomic_add(&port->write_waiting, 1); /* undo damage */
                        }
                        block = to_write;
                }
                else if (block < to_write) {
                        atomic_add(&port->write_waiting, 1); /* we will loop back */
                }
                switch (port->config.format) {
                case 0x24:      /*      floats  */
                        copy_float_to_short((short *)(port->wr_cur+port->was_written), (const float *)data,
                                block, !B_HOST_IS_LENDIAN == !port->config.big_endian);
                        bytes_xferred = block * 2;
                        break;
                case 0x02:      /*      shorts  */
                        if (!B_HOST_IS_LENDIAN == !port->config.big_endian) {
                                /*      we need to swap */
                                swap_copy((short *)(port->wr_cur+port->was_written), (const short *)data, block);
                                bytes_xferred = block;
                                break;
                        }
                        /*      else fall through to default case       */
                case 0x11:      /*      bytes   */
                default:
                        memcpy((void *)(port->wr_cur+port->was_written), data, block);
                        bytes_xferred = block;
                        break;
                }
                port->was_written += block;
                port->wr_silence = 0;

                release_spinlock(&port->wr_lock);
                restore_interrupts(cp);

                data = ((char *)data)+bytes_xferred;
                written += bytes_xferred;
                to_write -= block;
        }

        *nwritten = written;
        release_sem(port->wr_entry);

        return B_OK;
}


bool
dma_a_interrupt(
        cmedia_pci_dev * dev)
{
        bool ret = false;
        pcm_dev * port = &dev->pcm;
        volatile uchar * ptr;
        uint32 addr;
        uint32 offs;
        bigtime_t st = system_time();
        int32 ww;

#if 0
ddprintf(("cmedia_pci: dma_a 0x%x+0x%x\n", PCI_IO_RD_32((int)port->dma_a), PCI_IO_RD_32((int)port->dma_a+4)));
#endif
//      KTRACE(); /* */
        acquire_spinlock(&port->wr_lock);

        if (port->write_sem < 0) {
                kprintf("cmedia_pci: spurious DMA A interrupt!\n");
                release_spinlock(&port->wr_lock);
                return false;
        }
        /* copy possible silence into playback buffer */

        if (port->was_written > 0 && port->was_written < port->wr_size) {
                if (port->config.format == 0x11) {
                        memset((void *)(port->wr_cur+port->was_written), 0x80, port->wr_size-port->was_written);
                }
                else {
                        memset((void *)(port->wr_cur+port->was_written), 0, port->wr_size-port->was_written);
                }
        }

        /* because the system may be lacking and not hand us the */
        /* interrupt in time, we check which half is currently being */
        /* played, and set the pointer to the other half */

        addr = PCI_IO_RD_32((uint32)port->dma_a);
        if ((offs = addr-(uint32)port->card->low_phys) < port->wr_size) {
                ptr = port->wr_2;
        }
        else {
                ptr = port->wr_1;
        }
        port->wr_total += port->config.play_buf_size/2;
        /* compensate for interrupt latency */
        /* assuming 4 byte frames */
        port->wr_time = st-(offs&(port->config.play_buf_size/2-1))*250000LL/(int64)port->config.sample_rate;
        if ((ww = atomic_add(&port->wr_time_wait, -1)) > 0) {
                release_sem_etc(port->wr_time_sem, 1, B_DO_NOT_RESCHEDULE);
                ret = true;
        }
        else {
                atomic_add(&port->wr_time_wait, 1); /* re-set to 0 */
        }

        if (port->wr_cur == ptr) {
                port->wr_skipped++;
                OLDAPI(("cmedia_pci: write skipped %ld\n", port->wr_skipped));
        }
        port->wr_cur = ptr;
        port->was_written = 0;

        /* check for client there to write into buffer */

        if (atomic_add(&port->write_waiting, -1) > 0) {
#if DEBUG
                int_cnt++;
                the_time = st;
#endif
                release_sem_etc(port->write_sem, 1, B_DO_NOT_RESCHEDULE);
                ret = true;
        }
        else {
                atomic_add(&port->write_waiting, 1);
                /* if none there, fill with silence */
                if (port->wr_silence < (int32)port->config.play_buf_size * 2) {
                        if (port->config.format == 0x11) {
                                memset((void *)ptr, 0x80, port->wr_size);
                        }
                        else {
                                memset((void *)ptr, 0, port->wr_size);
                        }
                        port->wr_silence += port->wr_size;
                }
        }
        /* copying will be done in user thread */

        release_spinlock(&port->wr_lock);
        return ret;
}


bool
dma_c_interrupt(
        cmedia_pci_dev * dev)
{
        bool ret = false;
        pcm_dev * port = &dev->pcm;
        volatile uchar * ptr;
        uint32 addr;
        uint32 offs;
        int32 rr;
        bigtime_t st = system_time();

        /* mark data as readable in record buffer */
#if 0
ddprintf(("cmedia_pci: dma_c 0x%x+0x%x\n", PCI_IO_RD_32((int)port->dma_c), PCI_IO_RD_32((int)port->dma_c+4)));
#endif
//      KTRACE(); /* */
        acquire_spinlock(&port->rd_lock);

        if (port->read_sem < 0) {
                kprintf("cmedia_pci: spurious DMA C interrupt!\n");
                release_spinlock(&port->rd_lock);
                return false;
        }
        /* if we lose an interrupt, we automatically avoid constant glitching by setting */
        /* the write pointer based on where the DMA counter is everytime */
        addr = PCI_IO_RD_32((uint32)port->dma_c);
        if ((offs = addr-port->config.play_buf_size-(uint32)port->card->low_phys) < port->rd_size) {
                ptr = port->rd_2;
        }
        else {
                ptr = port->rd_1;
        }
        if (port->rd_cur == ptr) {
                port->rd_skipped++;
                OLDAPI(("cmedia_pci: read skipped %ld\n", port->rd_skipped));
        }
        port->rd_total += port->rd_size;

        port->rd_cur = ptr;
        port->was_read = 0;
        port->rd_time = port->next_rd_time;
        /*      time stamp when this buffer became available -- compensate for interrupt latency        */
        port->next_rd_time = st-(offs&(port->config.rec_buf_size/2-1))*1000000LL/(int64)port->config.sample_rate;
        if ((rr = atomic_add(&port->rd_time_wait, -1)) > 0) {
                release_sem_etc(port->rd_time_sem, 1, B_DO_NOT_RESCHEDULE);
                ret = true;
        }
        else {
                atomic_add(&port->rd_time_wait, 1); /* re-set to 0 */
        }

        if (atomic_add(&port->read_waiting, -1) > 0) {
                release_sem_etc(port->read_sem, 1, B_DO_NOT_RESCHEDULE);
                ret = true;
        }
        else {
                atomic_add(&port->read_waiting, 1);
        }
        /* copying will be done in the user thread */
        release_spinlock(&port->rd_lock);
        return ret;
}