diff mbox

[RFC] sparc32: add dbri audio device

Message ID 4E454D70.4070904@mc.net
State New
Headers show

Commit Message

Bob Breuer Aug. 12, 2011, 3:57 p.m. UTC
Here's a first look at adding the dbri audio device for sparc32.
For now, this is only usable with the SS-20 OBP rom, but I'm
looking at adding the slot probing to OpenBIOS to make it work
there.  It also needs to be adapted to the new memory api.  If
a bus for sbus was created, it should become possible to plug
this into and have it work with any of the sparc32 machines.

Only audio output is supported.  Tested with Debian 4.0 guest.

Comments

Blue Swirl Aug. 12, 2011, 9:17 p.m. UTC | #1
On Fri, Aug 12, 2011 at 3:57 PM, Bob Breuer <breuerr@mc.net> wrote:
> Here's a first look at adding the dbri audio device for sparc32.
> For now, this is only usable with the SS-20 OBP rom, but I'm
> looking at adding the slot probing to OpenBIOS to make it work
> there.  It also needs to be adapted to the new memory api.  If
> a bus for sbus was created, it should become possible to plug
> this into and have it work with any of the sparc32 machines.
>
> Only audio output is supported.  Tested with Debian 4.0 guest.

Nice work, this is already much better than cs4231. I have a few minor
comments below.

> diff --git a/Makefile.target b/Makefile.target
> index 096214a..680106f 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -328,6 +328,7 @@ else
>  obj-sparc-y = sun4m.o lance.o tcx.o sun4m_iommu.o slavio_intctl.o
>  obj-sparc-y += slavio_timer.o slavio_misc.o sparc32_dma.o
>  obj-sparc-y += cs4231.o eccmemctl.o sbi.o sun4c_intctl.o leon3.o
> +obj-sparc-y += dbri.o
>
>  # GRLIB
>  obj-sparc-y += grlib_gptimer.o grlib_irqmp.o grlib_apbuart.o
> diff --git a/hw/dbri.c b/hw/dbri.c
> new file mode 100644
> index 0000000..46f21e4
> --- /dev/null
> +++ b/hw/dbri.c
> @@ -0,0 +1,1342 @@
> +/*
> + * QEMU DBRI audio interface
> + *
> + * Copyright (c) 2011 Bob Breuer
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a copy
> + * of this software and associated documentation files (the "Software"), to deal
> + * in the Software without restriction, including without limitation the rights
> + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> + * copies of the Software, and to permit persons to whom the Software is
> + * furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> + * THE SOFTWARE.
> + */
> +
> +#include "hw.h"
> +#include "audio/audio.h"
> +#include "sysbus.h"
> +#include "sun4m.h"
> +
> +/*
> + *  DBRI (Dual Basic Rate ISDN)
> + *  interface with the audio codec (CS4215) in several SPARCstation models
> + *    SS20 (internal codec), SS10 (external codec), LX, ...
> + *
> + *  Documentation at: http://www.freesoft.org/Linux/DBRI/
> + *  Linux 2.6 driver: sound/sparc/dbri.[ch]
> + *  NetBSD driver: src/sys/dev/sbus/dbri*
> + *
> + *  Unimplemented:
> + *    volume control
> + *    audio record - alaw, ulaw for record?
> + *    isdn
> + *    iommu errors (sbus faults)
> + *    monitor pipes (for 8-bit stereo?)
> + */
> +
> +//#define DEBUG_DBRI
> +//#define DEBUG_PIPE
> +//#define DEBUG_DMA
> +//#define DEBUG_CODEC
> +
> +#ifdef DEBUG_DBRI
> +#define DBRI_DPRINTF(fmt, ...)                          \
> +    printf("DBRI: " fmt , ## __VA_ARGS__)
> +#else
> +#define DBRI_DPRINTF(fmt, ...)
> +#endif
> +
> +#ifdef DEBUG_PIPE
> +#define PIPE_DPRINTF(fmt, ...)                          \
> +    printf("DBRI Pipe: " fmt , ## __VA_ARGS__)
> +#else
> +#define PIPE_DPRINTF(fmt, ...)
> +#endif
> +
> +#ifdef DEBUG_DMA
> +#define DMA_DPRINTF(fmt, ...)                           \
> +    printf("DBRI Dma: " fmt , ## __VA_ARGS__)
> +#else
> +#define DMA_DPRINTF(fmt, ...)
> +#endif
> +
> +#ifdef DEBUG_CODEC
> +#define CODEC_DPRINTF(fmt, ...)                         \
> +    printf("CS4215: " fmt , ## __VA_ARGS__)
> +#else
> +#define CODEC_DPRINTF(fmt, ...)
> +#endif

Tracepoints are preferred these days over debugging printfs. That can
be done later.

> +
> +
> +typedef struct CS4215State {
> +    int data_mode;
> +    int freq;
> +    const int16_t *tbl;
> +
> +    uint8_t status;
> +    uint8_t data_format;
> +    uint8_t port_control;
> +    uint8_t settings[4];
> +} CS4215State;
> +
> +typedef struct DBRIPipeState {
> +    uint32_t setup, ptr, data;
> +    uint32_t in_desc, out_desc;
> +    uint32_t in_next, out_next;
> +} DBRIPipeState;
> +
> +typedef struct DBRIState {
> +    SysBusDevice busdev;
> +    QEMUSoundCard card;
> +    SWVoiceOut *voice_out;
> +
> +    qemu_irq irq;
> +    void *iommu;
> +
> +    uint32_t pio_default;
> +    int32_t codec_offset;
> +
> +    uint32_t reg[2];
> +    uint32_t pio;
> +    uint32_t cmdq_ptr; /* REG8 */
> +    uint32_t intq_ptr; /* REG9 */
> +    uint32_t intq_idx;
> +    uint32_t chi_global_mode;
> +    uint32_t chi_data_mode;
> +
> +    bool pipe_update;
> +    bool chi_active;
> +
> +    struct {
> +        uint32_t pipe, ctrl;
> +        int offset;
> +        bool stopped;
> +    } play, rec;
> +
> +    DBRIPipeState pipe[32];
> +
> +    CS4215State codec;
> +} DBRIState;
> +
> +
> +#define DBRI_ROM_SIZE   0x30
> +#define DBRI_REG_SIZE   0x100
> +#define DBRI_REG_OFFSET 0x10000
> +
> +/* bits in reg0 (status/control */
> +#define DBRI_COMMAND_VALID      (1 << 15)
> +#define DBRI_CHI_ACTIVATE       (1 << 4)
> +#define DBRI_SOFT_RESET         (1 << 0)
> +
> +/* reg1 = interrupt status */
> +#define DBRI_INT_STATUS         (1 << 0)
> +/* reg2 = PIO (Parallel I/O)
> + * 4 bits of I/O: high nibble=enable, low nibble = value
> + *  PIO0: 1=internal codec
> + *  PIO1: 0=codec reset                 default = low
> + *  PIO2: 1=external speakerbox
> + *  PIO3: codec D/C, 1=data, 0=control  default = high
> + *
> + *  0x09 = SS-20 internal, codec offset 8
> + *  0x0c = speakerbox?, codec offset 0
> + */
> +#define DBRI_PIO_EN             0xf0
> +#define DBRI_PIO3               0x08
> +#define DBRI_PIO2               0x04
> +#define DBRI_PIO1               0x02
> +#define DBRI_PIO0               0x01
> +#define DBRI_PIO_DEFAULT_INTERNAL   (DBRI_PIO3 | DBRI_PIO0)
> +#define DBRI_PIO_DEFAULT_EXTERNAL   (DBRI_PIO3 | DBRI_PIO2)
> +#define DBRI_CODEC_RESET(s)         (((s)->pio & 0x22) != 0x22)
> +#define DBRI_CODEC_DATA_MODE(s)     (((s)->pio & 0x88) != 0x80)
> +
> +/* interrupt codes */
> +#define INTR_BRDY               1   /* Receive buffer ready */
> +#define INTR_MINT               2   /* Marker interrupt in TD/RD */
> +#define INTR_EOL                5   /* End of list */
> +#define INTR_CMDI               6   /* Command has been read */
> +#define INTR_XCMP               8   /* Transmit complete */
> +#define INTR_FXDT               10  /* Fixed data change */
> +#define INTR_CHIL               11  /* CHI lost frame */
> +
> +/* ctrl is from the TX/RX descriptors */
> +#define DBRI_TXBUF_LEN(s)       (((s)->play.ctrl >> 16) & 0x1fff)
> +#define DBRI_RXBUF_LEN(s)       ((s)->rec.ctrl & 0x1fff)
> +
> +/* pipe setup from SDP command */
> +#define SDP_CHANGE              (2 << 18)  /* report any changes */
> +#define SDP_EOL                 (1 << 17)
> +#define SDP_MODE_MASK           (7 << 13)
> +#define SDP_MODE_FIXED          (6 << 13)
> +#define SDP_MODE_MEM            (0 << 13)
> +#define SDP_DIR_OUT             (1 << 12)  /* direction */
> +#define SDP_MSB                 (1 << 11)  /* bit order within byte */
> +#define SDP_PTR_VALID           (1 << 10)
> +#define SDP_ABORT               (1 << 8)
> +#define SDP_CLEAR               (1 << 7)
> +
> +#define SDP_MODE(setup)             ((setup) & SDP_MODE_MASK)
> +#define SDP_REPORT_CHANGE(setup)    ((setup) & SDP_CHANGE)
> +
> +/* time slot defined by DTS command */
> +#define DTS_VIN                 (1 << 17)  /* valid in */
> +#define DTS_VOUT                (1 << 16)  /* valid out */
> +#define DTS_INSERT              (1 << 15)  /* 1=insert, 0=delete */
> +#define DTS_PIPE_IN_PREV(dts)   (((dts) >> 10) & 0x1f)
> +#define DTS_PIPE_OUT_PREV(dts)  (((dts) >> 5) & 0x1f)
> +/* timeslot fields for in and out slots */
> +#define SLOT_LEN(ts)            (((ts) >> 24) & 0xff)
> +#define SLOT_START(ts)          (((ts) >> 14) & 0x3ff)
> +#define SLOT_MODE(ts)           (((ts) >> 10) & 0x7)
> +#define SLOT_MON(ts)            (((ts) >> 5) & 0x1f)
> +#define SLOT_NEXT(ts)           ((ts) & 0x1f)
> +
> +/* CHI global mode from CHI command */
> +#define DBRI_CHI_CLOCK(s)       (((s)->chi_global_mode >> 16) & 0xff)
> +#define DBRI_CHI_IS_MASTER(s)   (DBRI_CHI_CLOCK(s) >= 3)
> +
> +
> +/* audio codec */
> +/* control slot 1 = status */
> +#define CS4215_CLB              (1 << 2)  /* control latch bit */
> +#define CS4215_STATUS_RWMASK    0x1f
> +#define CS4215_STATUS_FIXED     0x20      /* upper 3 bits fixed at 001 */
> +#define CS4215_CLB_CLEAR(st)    (((st) & 0x1b) | 0x20)
> +
> +/* control slot 2 = data format */
> +#define CS4215_DF_MASK          (3 << 0)  /* data format */
> +#define CS4215_DF_S16           (0 << 0)
> +#define CS4215_DF_ULAW          (1 << 0)
> +#define CS4215_DF_ALAW          (2 << 0)
> +#define CS4215_DF_U8            (3 << 0)
> +#define CS4215_ST               (1 << 2)  /* stereo */
> +#define CS4215_DFR_MASK         (7 << 3)  /* data frequency rate */
> +#define CS4215_DFR_EXTRACT(d)   (((d) & CS4215_DFR_MASK) >> 3)
> +
> +/* control slot 3 = port control */
> +#define CS4215_XEN              (1 << 0)
> +#define CS4215_XCLK             (1 << 1)  /* transmit clock master mode */
> +#define CS4215_BSEL_MASK        (3 << 2)
> +#define CS4215_BSEL_256         (2 << 2)
> +#define CS4215_MCK_MASK         (7 << 4)  /* clock source select */
> +#define CS4215_MCK_XTAL1        (1 << 4)
> +#define CS4215_MCK_XTAL2        (2 << 4)

Enums are more friedly to GDB, but it's OK to leave them as #defines.

> +
> +#define CODEC_IS_MASTER(c)  \
> +            ((c)->data_mode && ((c)->port_control & CS4215_XCLK))
> +
> +/* 2 clocks, 8 clock dividers */
> +#define AUD_CLK1  24576000
> +#define AUD_CLK2  16934400
> +
> +static const int cs4215_clk_div[8] = {
> +    3072, 1536, 896, 768, 448, 384, 512, 2560
> +};
> +
> +/* MuLaw/ALaw tables are also in cs4231a.c, move to common code? */
> +/* Tables courtesy http://hazelware.luggle.com/tutorials/mulawcompression.html */
> +static const int16_t MuLawDecompressTable[256] =
> +{
> +     -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
> +     -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
> +     -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
> +     -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
> +      -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
> +      -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
> +      -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
> +      -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
> +      -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
> +      -1372, -1308, -1244, -1180, -1116, -1052,  -988,  -924,
> +       -876,  -844,  -812,  -780,  -748,  -716,  -684,  -652,
> +       -620,  -588,  -556,  -524,  -492,  -460,  -428,  -396,
> +       -372,  -356,  -340,  -324,  -308,  -292,  -276,  -260,
> +       -244,  -228,  -212,  -196,  -180,  -164,  -148,  -132,
> +       -120,  -112,  -104,   -96,   -88,   -80,   -72,   -64,
> +        -56,   -48,   -40,   -32,   -24,   -16,    -8,     0,
> +      32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
> +      23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
> +      15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
> +      11900, 11388, 10876, 10364,  9852,  9340,  8828,  8316,
> +       7932,  7676,  7420,  7164,  6908,  6652,  6396,  6140,
> +       5884,  5628,  5372,  5116,  4860,  4604,  4348,  4092,
> +       3900,  3772,  3644,  3516,  3388,  3260,  3132,  3004,
> +       2876,  2748,  2620,  2492,  2364,  2236,  2108,  1980,
> +       1884,  1820,  1756,  1692,  1628,  1564,  1500,  1436,
> +       1372,  1308,  1244,  1180,  1116,  1052,   988,   924,
> +        876,   844,   812,   780,   748,   716,   684,   652,
> +        620,   588,   556,   524,   492,   460,   428,   396,
> +        372,   356,   340,   324,   308,   292,   276,   260,
> +        244,   228,   212,   196,   180,   164,   148,   132,
> +        120,   112,   104,    96,    88,    80,    72,    64,
> +         56,    48,    40,    32,    24,    16,     8,     0
> +};
> +
> +static const int16_t ALawDecompressTable[256] =
> +{
> +     -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
> +     -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
> +     -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
> +     -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
> +     -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
> +     -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
> +     -11008,-10496,-12032,-11520,-8960, -8448, -9984, -9472,
> +     -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
> +     -344,  -328,  -376,  -360,  -280,  -264,  -312,  -296,
> +     -472,  -456,  -504,  -488,  -408,  -392,  -440,  -424,
> +     -88,   -72,   -120,  -104,  -24,   -8,    -56,   -40,
> +     -216,  -200,  -248,  -232,  -152,  -136,  -184,  -168,
> +     -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
> +     -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
> +     -688,  -656,  -752,  -720,  -560,  -528,  -624,  -592,
> +     -944,  -912,  -1008, -976,  -816,  -784,  -880,  -848,
> +      5504,  5248,  6016,  5760,  4480,  4224,  4992,  4736,
> +      7552,  7296,  8064,  7808,  6528,  6272,  7040,  6784,
> +      2752,  2624,  3008,  2880,  2240,  2112,  2496,  2368,
> +      3776,  3648,  4032,  3904,  3264,  3136,  3520,  3392,
> +      22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
> +      30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
> +      11008, 10496, 12032, 11520, 8960,  8448,  9984,  9472,
> +      15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
> +      344,   328,   376,   360,   280,   264,   312,   296,
> +      472,   456,   504,   488,   408,   392,   440,   424,
> +      88,    72,   120,   104,    24,     8,    56,    40,
> +      216,   200,   248,   232,   152,   136,   184,   168,
> +      1376,  1312,  1504,  1440,  1120,  1056,  1248,  1184,
> +      1888,  1824,  2016,  1952,  1632,  1568,  1760,  1696,
> +      688,   656,   752,   720,   560,   528,   624,   592,
> +      944,   912,  1008,   976,   816,   784,   880,   848
> +};
> +
> +/* reverse bits in a byte */
> +static uint8_t byte_rev(uint8_t b)
> +{
> +    b = ((b & 0xf0) >> 4) | ((b & 0x0f) << 4);
> +    b = ((b & 0xcc) >> 2) | ((b & 0x33) << 2);
> +    b = ((b & 0xaa) >> 1) | ((b & 0x55) << 1);
> +
> +    return b;
> +}
> +
> +static void cs4215_reset(CS4215State *s)
> +{
> +    s->status = CS4215_STATUS_FIXED | CS4215_CLB;
> +    s->data_format = CS4215_DF_ULAW;
> +    s->port_control = CS4215_BSEL_256 | CS4215_XEN;
> +    s->settings[0] = 0x3f;
> +    s->settings[1] = 0xbf;
> +    s->settings[2] = 0xc0;
> +    s->settings[3] = 0xf0;
> +}
> +
> +static void cs4215_setmode(CS4215State *s, int mode)
> +{
> +    int div;
> +
> +    s->data_mode = mode;
> +
> +    if (mode) {
> +        /* calculate frequency */
> +        div = cs4215_clk_div[CS4215_DFR_EXTRACT(s->data_format)];
> +
> +        switch (s->port_control & CS4215_MCK_MASK) {
> +        case CS4215_MCK_XTAL1:
> +            s->freq = AUD_CLK1 / div;
> +            break;
> +        case CS4215_MCK_XTAL2:
> +            s->freq = AUD_CLK2 / div;
> +            break;
> +        default:
> +            s->freq = 0;
> +            break;
> +        }
> +    } else {
> +        s->freq = 0;
> +        s->status = CS4215_CLB_CLEAR(s->status);
> +    }
> +}
> +
> +static void cs4215_getformat(CS4215State *s, struct audsettings *as)
> +{
> +    as->freq = s->freq;
> +    as->nchannels = (s->data_format & CS4215_ST) ? 2 : 1;
> +    as->endianness = AUDIO_HOST_ENDIANNESS;
> +
> +    CODEC_DPRINTF("audio format: %d Hz, %d chan, fmt %d\n",
> +        as->freq, as->nchannels, s->data_format & CS4215_DF_MASK);
> +
> +    s->tbl = NULL; /* conversion table */
> +
> +    switch (s->data_format & CS4215_DF_MASK) {
> +    case CS4215_DF_S16: /* 16-bit 2's complement */
> +        as->fmt = AUD_FMT_S16;
> +        as->endianness = 1;   /* big-endian */
> +        break;
> +    case CS4215_DF_ULAW: /* 8-bit Mu-law */
> +        s->tbl = MuLawDecompressTable;
> +        as->fmt = AUD_FMT_S16;
> +        break;
> +    case CS4215_DF_ALAW: /* 8-bit A-law */
> +        s->tbl = ALawDecompressTable;
> +        as->fmt = AUD_FMT_S16;
> +        break;
> +    case CS4215_DF_U8: /* 8-bit unsigned */
> +        as->fmt = AUD_FMT_U8;
> +        break;
> +    }
> +}
> +
> +static uint8_t cs4215_read(CS4215State *s, int slot)
> +{
> +    uint8_t val;
> +
> +    /* 1-based slot number */
> +    if (s->data_mode) {
> +        switch (slot) {
> +        case 5:
> +            val = s->settings[0];
> +            break;
> +        case 6:
> +            val = s->settings[1] & 0x7f;
> +            break;
> +        case 7:
> +            val = s->settings[2] & 0xdf;
> +            break;
> +        case 8:
> +            val = s->settings[3];
> +            break;
> +        default:
> +            val = 0;
> +            break;
> +        }
> +    } else {
> +        switch (slot) {
> +        case 1:
> +            val = s->status;
> +            break;
> +        case 2:
> +            val = s->data_format;
> +            break;
> +        case 3:
> +            val = s->port_control;
> +            break;
> +        case 4:
> +            val = 0; /* test */
> +            break;
> +        case 5:
> +            val = 0xc0; /* codec pio */
> +            break;
> +        case 7:
> +            val = 0x02; /* Rev E */
> +            break;
> +        default:
> +            val = 0;
> +            break;
> +        }
> +    }
> +    CODEC_DPRINTF("read 0x%02x from %s slot %d\n",
> +        val, s->data_mode ? "data" : "control", slot);
> +    return val;
> +}
> +
> +static void cs4215_write(CS4215State *s, int slot, uint8_t val)
> +{
> +    CODEC_DPRINTF("write 0x%02x to %s slot %d\n",
> +        val, s->data_mode ? "data" : "control", slot);
> +
> +    if (s->data_mode) {
> +        if (slot >= 5 && slot <= 8) {
> +            s->settings[slot-5] = val;

Please add spaces around '-'. I'd expect scripts/checkpatch.pl to complain.

> +        }
> +    } else {
> +        switch (slot) {
> +        case 1:
> +            s->status = (val & CS4215_STATUS_RWMASK) | CS4215_STATUS_FIXED;
> +            break;
> +        case 2:
> +            s->data_format = val;
> +            break;
> +        case 3:
> +            s->port_control = val;
> +            break;
> +        }
> +    }
> +}
> +
> +static uint32_t dbri_dma_readl(DBRIState *s, uint32_t addr)
> +{
> +    uint32_t val;
> +
> +    sparc_iommu_memory_read(s->iommu, addr, (uint8_t *)&val, 4);
> +    val = be32_to_cpu(val);
> +    DMA_DPRINTF("readl 0x%08x\n", val);
> +    return val;
> +}
> +
> +static void dbri_dma_writel(DBRIState *s, uint32_t addr, uint32_t val)
> +{
> +    DMA_DPRINTF("writel 0x%08x\n", val);
> +    val = cpu_to_be32(val);
> +    sparc_iommu_memory_write(s->iommu, addr, (uint8_t *)&val, 4);
> +}
> +
> +#ifdef DEBUG_DBRI
> +static const char *intr_code[16] = {
> +    "code 0", "BRDY", "MINT", "IBEG", "IEND", "EOL", "CMDI", "code 7",
> +    "XCMP", "SBRI", "FXDT", "CHIL/COLL", "DBYT", "RBYT", "LINT", "UNDR"
> +};
> +#endif
> +
> +static void dbri_post_interrupt(DBRIState *s, int chan, int code, int field)
> +{
> +    uint32_t addr, val;
> +
> +    addr = s->intq_ptr;
> +    if (!addr) {
> +        /* disabled */
> +        return;
> +    }
> +
> +    if (s->intq_idx == 64) {
> +        /* read next interrupt block from 1st word */
> +        addr = dbri_dma_readl(s, addr);
> +        if (!addr) {
> +            /* out of space? */
> +            return;
> +        }
> +        s->intq_ptr = addr;
> +        s->intq_idx = 1;
> +    }
> +
> +    field &= 0xfffff;
> +    val = 0x80000000 | (chan << 24) | (code << 20) | field;
> +
> +    DBRI_DPRINTF("interrupt (0x%08x) %s, chan %d, field 0x%05x\n",
> +        val, intr_code[code], chan, field);
> +
> +    addr += 4 * s->intq_idx;
> +    s->intq_idx++;
> +
> +    dbri_dma_writel(s, addr, val);
> +    s->reg[1] |= DBRI_INT_STATUS;
> +    qemu_irq_raise(s->irq);
> +}
> +
> +static void dbri_out_cb(void *opaque, int free)
> +{
> +    DBRIState *s = opaque;
> +    int pipe = s->play.pipe;
> +    int left, used;
> +    int len, buf_len;
> +    uint32_t buf_ptr;
> +    uint8_t tmpbuf[1024];
> +    int16_t linbuf[1024];
> +
> +    if (!pipe || s->play.stopped) {
> +        AUD_set_active_out(s->voice_out, 0);
> +        return;
> +    }
> +
> +    used = 0;
> +    while (free) {
> +        buf_len = DBRI_TXBUF_LEN(s);
> +        left = buf_len - s->play.offset;
> +
> +        /* end of buffer? */
> +        if (left <= 0) {
> +            uint32_t td_ptr = s->pipe[pipe].ptr;
> +
> +            if (!td_ptr) {
> +                /* bad descriptor pointer - flag a fault? */
> +                s->play.stopped = 1;
> +                break;
> +            }
> +
> +            if (s->pipe[pipe].data) {
> +                /* clear our data pointer to flag buffer done */
> +                s->pipe[pipe].data = 0;
> +
> +                /* update status */
> +                dbri_dma_writel(s, td_ptr+12, 0x01);
> +                if ((s->play.ctrl & 0x80008000) == 0x80008000) {
> +                    /* transmit complete */
> +                    dbri_post_interrupt(s, pipe, INTR_XCMP, 0);
> +                }
> +            }
> +            /* next descriptor */
> +            td_ptr = dbri_dma_readl(s, td_ptr+8);
> +            if (!td_ptr) {
> +                DBRI_DPRINTF("out of tx data after %d bytes\n", used);
> +                if (used) {
> +                    /*
> +                     * maybe the host buffers are too big,
> +                     * give the guest a chance to catch up
> +                     */
> +                    break;
> +                }
> +                if (s->pipe[pipe].setup & SDP_EOL) {
> +                    /* end of list */
> +                    dbri_post_interrupt(s, pipe, INTR_EOL, td_ptr);
> +                }
> +                s->play.stopped = 1;
> +                break;
> +            }
> +            s->pipe[pipe].ptr = td_ptr;
> +            s->play.ctrl = dbri_dma_readl(s, td_ptr);
> +            if (s->play.ctrl & 0x4000) {
> +                /* marker int */
> +                dbri_post_interrupt(s, pipe, INTR_MINT, td_ptr);
> +            }
> +            /* setup next buffer */
> +            s->pipe[pipe].data = dbri_dma_readl(s, td_ptr+4);
> +            s->play.offset = 0;
> +            if (!s->pipe[pipe].data) {
> +                /* NULL data pointer, what now? */
> +            }
> +            left = buf_len = DBRI_TXBUF_LEN(s);
> +
> +            DBRI_DPRINTF("play buffer @ 0x%08x, len %d\n",
> +                s->pipe[pipe].data, buf_len);
> +        }
> +
> +        if (!s->pipe[pipe].data) {
> +            /* bad data pointer - flag a fault? */
> +            s->play.stopped = 1;
> +            break;
> +        }
> +        buf_ptr = s->pipe[pipe].data + s->play.offset;
> +        len = audio_MIN(left, sizeof(tmpbuf));
> +
> +        if (s->codec.tbl) {
> +            /* convert from A/Mu-Law to 16-bit linear */
> +            const int16_t *tbl;
> +            int i;
> +
> +            len = audio_MIN(len, free >> 1);
> +
> +            sparc_iommu_memory_read(s->iommu, buf_ptr, tmpbuf, len);
> +
> +            tbl = s->codec.tbl;
> +            for (i = 0; i < len; i++) {
> +                linbuf[i] = tbl[tmpbuf[i]];
> +            }
> +            len = AUD_write(s->voice_out, linbuf, len << 1);
> +            len >>= 1;
> +        } else {
> +            len = audio_MIN(len, free);
> +
> +            sparc_iommu_memory_read(s->iommu, buf_ptr, tmpbuf, len);
> +
> +            len = AUD_write(s->voice_out, tmpbuf, len);
> +        }
> +
> +        if (!len) {
> +            break;
> +        }
> +
> +        s->play.offset += len;
> +        free -= len;
> +        used += len;
> +    }
> +}
> +
> +static void dbri_update_audio(DBRIState *s)
> +{
> +    struct audsettings as;
> +
> +    cs4215_getformat(&s->codec, &as);
> +    s->voice_out = AUD_open_out(&s->card, s->voice_out,
> +                                "dbri_out", s, dbri_out_cb, &as);
> +
> +    /* zero is not a valid pipe */
> +    if (s->play.pipe && !s->play.stopped) {
> +        AUD_set_active_out(s->voice_out, 1);
> +    } else {
> +        AUD_set_active_out(s->voice_out, 0);
> +    }
> +}
> +
> +static void dbri_start_audio_out(DBRIState *s, int pipe)
> +{
> +    uint32_t td_ptr;  /* transmit descriptor */
> +
> +    if (pipe == s->play.pipe && !s->play.stopped) {
> +        return;
> +    }
> +    s->play.pipe = pipe;
> +
> +    /* ptr was validated by caller */
> +    td_ptr = s->pipe[pipe].ptr;
> +
> +    s->play.ctrl = dbri_dma_readl(s, td_ptr);   /* control/length */
> +    if (s->play.ctrl & 0x4000) {
> +        /* marker int */
> +        dbri_post_interrupt(s, pipe, INTR_MINT, td_ptr);
> +    }
> +    /* get data pointer, re-use pipe data field */
> +    s->pipe[pipe].data = dbri_dma_readl(s, td_ptr+4);
> +
> +    /* prepare buffer */
> +    s->play.offset = 0;
> +    s->play.stopped = 0;
> +
> +    DBRI_DPRINTF("play buffer @ 0x%08x, len %d\n",
> +        s->pipe[pipe].data, DBRI_TXBUF_LEN(s));
> +
> +    dbri_update_audio(s);
> +}
> +
> +static void dbri_start_audio_in(DBRIState *s, int pipe)
> +{
> +    uint32_t rd_ptr;  /* receive descriptor */
> +
> +    if (pipe == s->rec.pipe && !s->rec.stopped) {
> +        return;
> +    }
> +    s->rec.pipe = pipe;
> +
> +    /* setup format */
> +
> +    rd_ptr = s->pipe[pipe].ptr;
> +
> +    s->rec.ctrl = dbri_dma_readl(s, rd_ptr+12); /* control/length */
> +    if (s->rec.ctrl & 0x4000) {
> +        /* marker int */
> +        dbri_post_interrupt(s, pipe, INTR_MINT, rd_ptr);
> +    }
> +    /* get data pointer, re-use pipe data field */
> +    s->pipe[pipe].data = dbri_dma_readl(s, rd_ptr+4);
> +
> +    /* prepare buffer */
> +    s->rec.offset = 0;
> +    s->rec.stopped = 0;
> +
> +    /* TODO: audio record, this is just a placeholder */
> +    if (0) {
> +        /* write buffer */
> +        uint32_t len = DBRI_RXBUF_LEN(s);
> +
> +        /* update status - completed and EOF */
> +        dbri_dma_writel(s, rd_ptr, 0xc0000000 | (len << 16));
> +        if (s->rec.ctrl & 0x8000) {
> +            /* buffer ready */
> +            dbri_post_interrupt(s, pipe, INTR_BRDY, rd_ptr);
> +        }
> +        /* next */
> +        rd_ptr = dbri_dma_readl(s, rd_ptr+8);
> +        s->pipe[pipe].ptr = rd_ptr;
> +    }
> +    if (s->pipe[pipe].setup & SDP_EOL) {
> +        /* end of list */
> +        dbri_post_interrupt(s, pipe, INTR_EOL, rd_ptr);
> +    }
> +    s->rec.stopped = 1;
> +    dbri_update_audio(s);
> +}
> +
> +static void dbri_stop_audio(DBRIState *s, int pipe, int abort)
> +{
> +    if (pipe == s->play.pipe) {
> +        if (abort && s->pipe[pipe].ptr) {
> +            /* abort */
> +            dbri_dma_writel(s, s->pipe[pipe].ptr+12, 0x04);
> +            AUD_set_active_out(s->voice_out, 0);
> +        }
> +        s->play.stopped = 1;
> +        s->play.pipe = 0;
> +    }
> +    if (pipe == s->rec.pipe) {
> +        if (abort && s->pipe[pipe].ptr) {
> +            /* abort */
> +            dbri_dma_writel(s, s->pipe[pipe].ptr, 0x20);
> +        }
> +        s->rec.stopped = 1;
> +        s->rec.pipe = 0;
> +    }
> +}
> +
> +static void dbri_update_chi_status(DBRIState *s)
> +{
> +    if ((s->reg[0] & DBRI_CHI_ACTIVATE)
> +        && (DBRI_CHI_IS_MASTER(s) || CODEC_IS_MASTER(&s->codec))) {
> +        s->chi_active = 1;
> +    } else {
> +        s->chi_active = 0;
> +    }
> +}
> +
> +static void dbri_run_pipes(DBRIState *s)
> +{
> +    int i, start, len, codec_slot;
> +    uint32_t val;
> +    uint32_t been_here;
> +
> +    if (!s->chi_active) {
> +        return; /* CHI not active */
> +    }
> +
> +    /* run through the pipes */
> +    s->pipe_update = 0;
> +
> +    /* pipe 16 is the anchor where the CHI starts and ends */
> +    if (s->chi_data_mode & 0x02) {
> +        i = s->pipe[16].out_next;
> +        been_here = 1 << 16;
> +        PIPE_DPRINTF("OUT pipes:\n");
> +        while (i != 16) {
> +            if (been_here & (1 << i)) {
> +                PIPE_DPRINTF("linked list loop before OUT anchor\n");
> +                break;
> +            }
> +            been_here |= 1 << i;
> +            start = SLOT_START(s->pipe[i].out_desc);
> +            len = SLOT_LEN(s->pipe[i].out_desc);
> +
> +            /* use bits per frame instead of 0 for OUT pipes */
> +            if (start >= 64) {
> +                start = 0;
> +            }
> +            PIPE_DPRINTF(" %d, 0x%08x = start %d, len %d\n",
> +                i, s->pipe[i].out_desc, start, len);
> +
> +            switch (SDP_MODE(s->pipe[i].setup)) {
> +            case SDP_MODE_MEM:
> +                PIPE_DPRINTF("  TD @ 0x%08x\n", s->pipe[i].ptr);
> +
> +                if (s->pipe[i].ptr && start == s->codec_offset) {
> +                    dbri_start_audio_out(s, i);
> +                }
> +                break;
> +
> +            case SDP_MODE_FIXED:
> +                PIPE_DPRINTF("  Fixed 0x%08x\n", s->pipe[i].data);
> +
> +                /* assume 8-bit alignment */
> +                codec_slot = 1 + (start - s->codec_offset) / 8;
> +                /* fixed pipe is LSB first, codec is MSB first */
> +                val = s->pipe[i].data;
> +                do {
> +                    cs4215_write(&s->codec, codec_slot, byte_rev(val & 0xff));
> +                    codec_slot++;
> +                    val >>= 8;
> +                    len -= 8;
> +                } while (len > 0);
> +                break;
> +            }
> +            i = s->pipe[i].out_next;
> +        }
> +    }
> +
> +    i = s->pipe[16].in_next;
> +    been_here = 1 << 16;
> +    PIPE_DPRINTF("IN pipes:\n");
> +    while (i != 16) {
> +        if (been_here & (1 << i)) {
> +            PIPE_DPRINTF("linked list loop before IN anchor\n");
> +            break;
> +        }
> +        been_here |= 1 << i;
> +        start = SLOT_START(s->pipe[i].in_desc);
> +        len = SLOT_LEN(s->pipe[i].in_desc);
> +
> +        PIPE_DPRINTF(" %d, 0x%08x = start %d, len %d\n",
> +            i, s->pipe[i].in_desc, start, len);
> +
> +        switch (SDP_MODE(s->pipe[i].setup)) {
> +        case SDP_MODE_MEM:
> +            PIPE_DPRINTF("  RD @ 0x%08x\n", s->pipe[i].ptr);
> +
> +            if (s->pipe[i].ptr && start == s->codec_offset) {
> +                dbri_start_audio_in(s, i);
> +            }
> +            break;
> +
> +        case SDP_MODE_FIXED:
> +            /* assume 8-bit alignment */
> +            codec_slot = 1 + (start - s->codec_offset) / 8;
> +            /* fixed pipe is LSB first, codec is MSB first */
> +            val = byte_rev(cs4215_read(&s->codec, codec_slot));
> +            if (len > 8) {
> +                val |= byte_rev(cs4215_read(&s->codec, codec_slot+1)) << 8;
> +            }
> +            if (s->pipe[i].data != val) {
> +                s->pipe[i].data = val;
> +                if (SDP_REPORT_CHANGE(s->pipe[i].setup)) {
> +                    dbri_post_interrupt(s, i, INTR_FXDT, val);
> +                }
> +            }
> +            PIPE_DPRINTF("  Fixed 0x%08x\n", s->pipe[i].data);
> +            break;
> +        }
> +        i = s->pipe[i].in_next;
> +    }
> +}
> +
> +static void dbri_cmd_pause(DBRIState *s, uint32_t *cmd)
> +{
> +    dbri_run_pipes(s);
> +}
> +
> +static void dbri_cmd_jump(DBRIState *s, uint32_t *cmd)
> +{
> +    s->cmdq_ptr = cmd[1];
> +}
> +
> +/* initialize interrupt queue */
> +static void dbri_cmd_iiq(DBRIState *s, uint32_t *cmd)
> +{
> +    s->intq_ptr = cmd[1];
> +    s->intq_idx = 1;
> +}
> +
> +/* setup data pipe, set data pointer */
> +static void dbri_cmd_sdp(DBRIState *s, uint32_t *cmd)
> +{
> +    int i = cmd[0] & 0x1f;
> +
> +    PIPE_DPRINTF("Setup pipe %d for %s: mode %d, IRM 0x%x, clear %d\n",
> +        i, (cmd[0] & SDP_DIR_OUT) ? "output" : "input",
> +        (cmd[0] >> 13) & 7, (cmd[0] >> 16) & 0xf,
> +        (cmd[0] >> 7) & 1);
> +
> +    s->pipe[i].setup = cmd[0];
> +    if (cmd[0] & (SDP_PTR_VALID | SDP_CLEAR | SDP_ABORT)) {
> +        /* stop any audio on this pipe */
> +        if (i) {
> +            dbri_stop_audio(s, i, cmd[0] & SDP_ABORT);
> +        }
> +    }
> +    if (cmd[0] & SDP_PTR_VALID) {
> +        PIPE_DPRINTF(" pipe pointer = 0x%08x\n", cmd[1]);
> +        s->pipe[i].ptr = cmd[1];
> +    }
> +}
> +
> +/* continue data pipe */
> +static void dbri_cmd_cdp(DBRIState *s, uint32_t *cmd)
> +{
> +    int i = cmd[0] & 0x1f;
> +
> +    if (i == s->play.pipe) {
> +        s->play.stopped = 0;
> +    }
> +    if (i == s->rec.pipe) {
> +        s->rec.stopped = 0;
> +    }
> +    dbri_update_audio(s);
> +}
> +
> +/* define time slot */
> +static void dbri_cmd_dts(DBRIState *s, uint32_t *cmd)
> +{
> +    int prev, next;
> +    int i = cmd[0] & 0x1f;
> +
> +    PIPE_DPRINTF("%s time slots for pipe %d\n",
> +        (cmd[0] & DTS_INSERT) ? "add/modify" : "delete", i);
> +
> +    if (cmd[0] & DTS_VIN) {
> +        prev = DTS_PIPE_IN_PREV(cmd[0]);
> +        if (cmd[0] & DTS_INSERT) {
> +            next = SLOT_NEXT(cmd[1]);
> +
> +            s->pipe[i].in_next = next;
> +            s->pipe[prev].in_next = i;
> +            s->pipe[i].in_desc = cmd[1];
> +        } else {
> +            /* delete */
> +            next = s->pipe[i].in_next;
> +            s->pipe[prev].in_next = next;
> +        }
> +
> +        PIPE_DPRINTF("In:  prev=%d, next=%d, mode=%d, len=%d, cycle=%d\n",
> +            prev, next, SLOT_MODE(cmd[1]),
> +            SLOT_LEN(cmd[1]), SLOT_START(cmd[1]));
> +    }
> +    if (cmd[0] & DTS_VOUT) {
> +        prev = DTS_PIPE_OUT_PREV(cmd[0]);
> +        if (cmd[0] & DTS_INSERT) {
> +            next = SLOT_NEXT(cmd[2]);
> +
> +            s->pipe[i].out_next = next;
> +            s->pipe[prev].out_next = i;
> +            s->pipe[i].out_desc = cmd[2];
> +        } else {
> +            /* delete */
> +            next = s->pipe[i].out_next;
> +            s->pipe[prev].out_next = next;
> +        }
> +
> +        PIPE_DPRINTF("Out: prev=%d, next=%d, mode=%d, len=%d, cycle=%d\n",
> +            prev, next, SLOT_MODE(cmd[2]),
> +            SLOT_LEN(cmd[2]), SLOT_START(cmd[2]));
> +    }
> +}
> +
> +/* set short pipe data */
> +static void dbri_cmd_ssp(DBRIState *s, uint32_t *cmd)
> +{
> +    unsigned int i = cmd[0] & 0x1f;
> +
> +    /* short pipe only */
> +    if (i > 16) {
> +        s->pipe[i].data = cmd[1];
> +    }
> +}
> +
> +/* set CHI global mode */
> +static void dbri_cmd_chi(DBRIState *s, uint32_t *cmd)
> +{
> +    int active;
> +    uint32_t status;
> +
> +    active = DBRI_CHI_IS_MASTER(s) || CODEC_IS_MASTER(&s->codec);
> +    s->chi_global_mode = cmd[0];
> +
> +    if (s->chi_global_mode & 0x8000) {
> +        /* report status */
> +        status = s->chi_data_mode & 0x03;
> +        if (!active) {
> +            status |= 0x04;
> +        }
> +        dbri_post_interrupt(s, 36, INTR_CHIL, status);
> +    }
> +    dbri_update_chi_status(s);
> +}
> +
> +/* set CHI data mode */
> +static void dbri_cmd_cdm(DBRIState *s, uint32_t *cmd)
> +{
> +    s->chi_data_mode = cmd[0];
> +}
> +
> +static const struct {
> +    const char *name;
> +    int len;
> +    void(*action)(DBRIState *, uint32_t *);
> +} command_list[16] = {
> +    { "WAIT",  0, NULL              },
> +    { "PAUSE", 4, dbri_cmd_pause    },
> +    { "JUMP",  4, dbri_cmd_jump     },
> +    { "IIQ",   8, dbri_cmd_iiq      },  /* Initialize Interrupt Queue */
> +    { "REX",   4, NULL              },  /* Report command EXecution   */
> +    { "SDP",   8, dbri_cmd_sdp      },  /* Setup Data Pipe            */
> +    { "CDP",   4, dbri_cmd_cdp      },  /* Continue Data Pipe         */
> +    { "DTS",  12, dbri_cmd_dts      },  /* Define Time Slot           */
> +    { "SSP",   8, dbri_cmd_ssp      },  /* Set Short Pipe             */
> +    { "CHI",   4, dbri_cmd_chi      },  /* Set CHI Global Mode        */
> +    { "NT",    4, NULL              },
> +    { "TE",    4, NULL              },
> +    { "CDEC",  4, NULL              },  /* Codec Setup                */
> +    { "TEST", 12, NULL              },
> +    { "CDM",   4, dbri_cmd_cdm      },  /* Set CHI Data Mode          */
> +    { "Reserved", -1, NULL          }
> +};
> +
> +static void dbri_run_commands(DBRIState *s)
> +{
> +    uint32_t cmd[3];
> +    uint32_t val;
> +    int stopped, i;
> +
> +    for (stopped = 0; !stopped; ) {
> +        cmd[0] = dbri_dma_readl(s, s->cmdq_ptr);
> +        i = cmd[0] >> 28;
> +
> +        /* interrupt on command? */
> +        if (cmd[0] & (1<<27)) {
> +            val = (i << 16) | (cmd[0] & 0xffff);
> +            dbri_post_interrupt(s, 38, INTR_CMDI, val);
> +        }
> +
> +        DBRI_DPRINTF("cmd %s = 0x%08x\n", command_list[i].name, cmd[0]);
> +
> +        switch (command_list[i].len) {
> +        case 12:
> +            cmd[2] = dbri_dma_readl(s, s->cmdq_ptr+8);

Here...

> +        case 8:
> +            cmd[1] = dbri_dma_readl(s, s->cmdq_ptr+4);

... and here, either a 'break' or a comment about fall through is missing.

> +        case 4:
> +            s->cmdq_ptr += command_list[i].len;
> +            s->pipe_update = 1;
> +            if (command_list[i].action) {
> +                command_list[i].action(s, cmd);
> +            }
> +            break;
> +        case 0:
> +        default:
> +            stopped = 1;
> +            s->reg[0] &= ~DBRI_COMMAND_VALID;
> +            if (s->pipe_update) {
> +                dbri_run_pipes(s);
> +            }
> +            break;
> +        }
> +    }
> +}
> +
> +static void dbri_reset(DeviceState *dev)
> +{
> +    DBRIState *s = container_of(dev, DBRIState, busdev.qdev);
> +    int i;
> +
> +    AUD_set_active_out(s->voice_out, 0);
> +    qemu_irq_lower(s->irq);
> +
> +    /* set defaults */
> +    s->reg[0] = 0x4008;
> +    s->reg[1] = 0;
> +    if (s->codec_offset) {
> +        s->pio_default = DBRI_PIO_DEFAULT_INTERNAL;
> +    } else {
> +        s->pio_default = DBRI_PIO_DEFAULT_EXTERNAL;
> +    }
> +    s->pio = s->pio_default;
> +    /* reset pointers */
> +    s->cmdq_ptr = 0;
> +    s->intq_ptr = 0;
> +
> +    s->chi_global_mode = 0;
> +    s->chi_data_mode = 0;
> +    s->chi_active = 0;
> +
> +    /* reset linked list */
> +    s->pipe[16].in_next = 16;
> +    s->pipe[16].out_next = 16;
> +
> +    /* clear all pipes */
> +    s->pipe_update = 0;
> +    for (i = 0; i < 32; i++) {
> +        s->pipe[i].setup = 0;
> +        s->pipe[i].ptr = 0;
> +        s->pipe[i].data = 0;
> +    }
> +    s->play.pipe = 0;
> +    s->rec.pipe = 0;
> +    s->play.stopped = 1;
> +    s->rec.stopped = 1;
> +
> +    cs4215_reset(&s->codec);
> +    cs4215_setmode(&s->codec, DBRI_CODEC_DATA_MODE(s));
> +}
> +
> +/* registers */
> +static uint32_t dbri_reg_readl(void *opaque, target_phys_addr_t addr)
> +{
> +    DBRIState *s = opaque;
> +    int val;
> +
> +    switch (addr) {
> +    case 0x00: /* Status and Control */
> +        val = s->reg[0];
> +        break;
> +    case 0x04: /* Mode and Interrupt */
> +        val = s->reg[1];
> +        if (val) {
> +            /* clear interrupt status */
> +            s->reg[1] = 0;
> +            qemu_irq_lower(s->irq);
> +        }
> +        break;
> +    case 0x08: /* I/O */
> +        val = s->pio;
> +        break;
> +    case 0x20: /* Command Queue Pointer */
> +        val = s->cmdq_ptr;
> +        break;
> +    case 0x24: /* Interrupt Queue Pointer */
> +        val = s->intq_ptr;
> +        break;
> +    default:
> +        val = 0;
> +        break;
> +    }
> +
> +    DBRI_DPRINTF("readl 0x%08x from reg " TARGET_FMT_plx "\n", val, addr);
> +
> +    return val;
> +}
> +
> +static void dbri_reg_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
> +{
> +    DBRIState *s = opaque;
> +
> +    DBRI_DPRINTF("writel 0x%08x to reg " TARGET_FMT_plx "\n", val, addr);
> +
> +    switch (addr) {
> +    case 0x00: /* Status and Control */
> +        s->reg[0] = val;
> +        if (val & DBRI_SOFT_RESET) {
> +            dbri_reset(&s->busdev.qdev);
> +        } else {
> +            dbri_update_chi_status(s);
> +            if (val & (DBRI_COMMAND_VALID | DBRI_CHI_ACTIVATE)) {
> +                dbri_run_commands(s);
> +            }
> +        }
> +        break;
> +    case 0x08: /* I/O */
> +        s->pio = (val & DBRI_PIO_EN) ? val : s->pio_default;
> +        if (DBRI_CODEC_RESET(s)) {
> +            cs4215_reset(&s->codec);
> +        }
> +        cs4215_setmode(&s->codec, DBRI_CODEC_DATA_MODE(s));
> +        dbri_update_chi_status(s);
> +        break;
> +    case 0x20: /* Command Queue Pointer */
> +        s->cmdq_ptr = val;
> +        s->reg[0] |= DBRI_COMMAND_VALID;
> +        dbri_run_commands(s);
> +        break;
> +    default:
> +        break;
> +    }
> +}
> +
> +static CPUReadMemoryFunc * const dbri_reg_read[3] = {
> +    NULL,
> +    NULL,
> +    dbri_reg_readl,
> +};
> +
> +static CPUWriteMemoryFunc * const dbri_reg_write[3] = {
> +    NULL,
> +    NULL,
> +    dbri_reg_writel,
> +};
> +
> +static int vmstate_dbri_post_load(void *opaque, int version_id)
> +{
> +    DBRIState *s = opaque;
> +
> +    cs4215_setmode(&s->codec, DBRI_CODEC_DATA_MODE(s));
> +    dbri_update_chi_status(s);
> +
> +    if (s->intq_ptr && s->reg[1]) {
> +        qemu_irq_raise(s->irq);
> +    }
> +
> +    /* resume playback */
> +    dbri_update_audio(s);
> +
> +    return 0;
> +}
> +
> +
> +/* the FCode rom */

I think the FCode sources are not very big, so they should be included
here as comment.

> +static const uint8_t dbri_rom[DBRI_ROM_SIZE] = {
> +    0xfd, 0x00, 0x09, 0xea, 0x00, 0x00, 0x00, 0x30, 0x12, 0x0a,
> +    'S',  'U',  'N',  'W',  ',',  'D',  'B',  'R',  'I',  'e',
> +    0x01, 0x14, 0x12, 0x04, 'n',  'a',  'm',  'e',  0x01, 0x10,
> +    0x01, 0x02, 0xa5, 0xa6, 0x7d, 0x1e, 0x01, 0x03, 0xa6, 0x80,
> +    0x01, 0x16, 0xa8, 0x63, 0xa5, 0x01, 0x17, 0x00
> +};
> +
> +/* everything is an offset within our sbus slot */
> +static void dbri_sbus_map(SysBusDevice *dev, target_phys_addr_t base)
> +{
> +    DBRIState *s = FROM_SYSBUS(DBRIState, dev);
> +    int rom, regs;
> +
> +    rom = qemu_ram_alloc(NULL, "dbri.rom", DBRI_ROM_SIZE);
> +    /* the rom is at offset 0 */
> +    cpu_register_physical_memory(base, DBRI_ROM_SIZE, rom|IO_MEM_ROM);
> +    cpu_physical_memory_write_rom(base, dbri_rom, DBRI_ROM_SIZE);
> +
> +    /* mirror at 0x1000, where the SS-20 bootrom looks for it */
> +    cpu_register_physical_memory(base+0x1000, DBRI_ROM_SIZE, rom|IO_MEM_ROM);
> +
> +    regs = cpu_register_io_memory(dbri_reg_read, dbri_reg_write, s,
> +        DEVICE_NATIVE_ENDIAN);
> +    cpu_register_physical_memory(base+DBRI_REG_OFFSET, DBRI_REG_SIZE, regs);
> +}
> +
> +static int dbri_init1(SysBusDevice *dev)
> +{
> +    DBRIState *s = FROM_SYSBUS(DBRIState, dev);
> +
> +    sysbus_init_mmio_cb(dev, DBRI_REG_OFFSET+DBRI_REG_SIZE, dbri_sbus_map);
> +    sysbus_init_irq(dev, &s->irq);
> +
> +    AUD_register_card("sun_dbri", &s->card);
> +
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_dbri_pipe = {
> +    .name = "dbri_pipe",
> +    .version_id = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(setup,    DBRIPipeState),
> +        VMSTATE_UINT32(ptr,      DBRIPipeState),
> +        VMSTATE_UINT32(data,     DBRIPipeState),
> +        VMSTATE_UINT32(in_desc,  DBRIPipeState),
> +        VMSTATE_UINT32(out_desc, DBRIPipeState),
> +        VMSTATE_UINT32(in_next,  DBRIPipeState),
> +        VMSTATE_UINT32(out_next, DBRIPipeState),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static const VMStateDescription vmstate_dbri = {
> +    .name = "dbri",
> +    .version_id = 1,
> +    .post_load = vmstate_dbri_post_load,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(reg[0],   DBRIState),
> +        VMSTATE_UINT32(reg[1],   DBRIState),
> +        VMSTATE_UINT32(pio,      DBRIState),
> +        VMSTATE_UINT32(cmdq_ptr, DBRIState),
> +        VMSTATE_UINT32(intq_ptr, DBRIState),
> +        VMSTATE_UINT32(intq_idx, DBRIState),
> +        VMSTATE_UINT32(chi_global_mode, DBRIState),
> +        VMSTATE_UINT32(chi_data_mode, DBRIState),
> +
> +        VMSTATE_UINT32(play.pipe,  DBRIState),
> +        VMSTATE_UINT32(rec.pipe,   DBRIState),
> +        VMSTATE_UINT32(play.ctrl,  DBRIState),
> +        VMSTATE_UINT32(rec.ctrl,   DBRIState),
> +        VMSTATE_BOOL(play.stopped, DBRIState),
> +        VMSTATE_BOOL(rec.stopped,  DBRIState),
> +
> +        VMSTATE_BOOL(pipe_update,  DBRIState),
> +        VMSTATE_STRUCT_ARRAY(pipe, DBRIState, 32, 1,
> +                             vmstate_dbri_pipe, DBRIPipeState),
> +
> +        VMSTATE_UINT8(codec.status,         DBRIState),
> +        VMSTATE_UINT8(codec.data_format,    DBRIState),
> +        VMSTATE_UINT8(codec.port_control,   DBRIState),
> +        VMSTATE_UINT8_ARRAY(codec.settings, DBRIState, 4),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static SysBusDeviceInfo dbri_info = {
> +    .init = dbri_init1,
> +    .qdev.name  = "SUNW,DBRIe",
> +    .qdev.desc  = "Sun DBRI audio interface",
> +    .qdev.size  = sizeof(DBRIState),
> +    .qdev.reset = dbri_reset,
> +    .qdev.vmsd  = &vmstate_dbri,
> +    .qdev.props = (Property[]) {
> +        DEFINE_PROP_PTR("iommu_opaque",   DBRIState, iommu),
> +        DEFINE_PROP_INT32("codec_offset", DBRIState, codec_offset, 8),
> +        DEFINE_PROP_END_OF_LIST(),
> +    }
> +};
> +
> +static void dbri_register_devices(void)
> +{
> +    sysbus_register_withprop(&dbri_info);
> +}
> +
> +device_init(dbri_register_devices);
> diff --git a/hw/sun4m.c b/hw/sun4m.c
> index df3aa32..3112d19 100644
> --- a/hw/sun4m.c
> +++ b/hw/sun4m.c
> @@ -421,6 +421,20 @@ static void lance_init(NICInfo *nd, target_phys_addr_t leaddr,
>     qdev_connect_gpio_out(dma_opaque, 0, reset);
>  }
>
> +static void dbri_init(target_phys_addr_t daddr, qemu_irq parent_irq,
> +                      void *iommu)
> +{
> +    DeviceState *dev;
> +    SysBusDevice *s;
> +
> +    dev = qdev_create(NULL, "SUNW,DBRIe");
> +    qdev_prop_set_ptr(dev, "iommu_opaque", iommu);
> +    qdev_init_nofail(dev);
> +    s = sysbus_from_qdev(dev);
> +    sysbus_connect_irq(s, 0, parent_irq);
> +    sysbus_mmio_map(s, 0, daddr);
> +}
> +
>  static DeviceState *slavio_intctl_init(target_phys_addr_t addr,
>                                        target_phys_addr_t addrg,
>                                        qemu_irq **parent_irq)
> @@ -943,10 +957,7 @@ static void sun4m_hw_init(const struct sun4m_hwdef *hwdef, ram_addr_t RAM_size,
>
>     if (hwdef->dbri_base) {
>         /* ISDN chip with attached CS4215 audio codec */
> -        /* prom space */
> -        empty_slot_init(hwdef->dbri_base+0x1000, 0x30);
> -        /* reg space */
> -        empty_slot_init(hwdef->dbri_base+0x10000, 0x100);
> +        dbri_init(hwdef->dbri_base, slavio_irq[11], iommu);
>     }
>
>     if (hwdef->bpp_base) {
>
Mark Cave-Ayland Aug. 15, 2011, 10:18 a.m. UTC | #2
On 12/08/11 16:57, Bob Breuer wrote:

> Here's a first look at adding the dbri audio device for sparc32.
> For now, this is only usable with the SS-20 OBP rom, but I'm
> looking at adding the slot probing to OpenBIOS to make it work
> there.  It also needs to be adapted to the new memory api.  If
> a bus for sbus was created, it should become possible to plug
> this into and have it work with any of the sparc32 machines.
>
> Only audio output is supported.  Tested with Debian 4.0 guest.

Very interesting indeed. As always, I'll try my best to provide some 
pointers for the OpenBIOS parts of this over on the openbios mailing list.


ATB,

Mark.
diff mbox

Patch

diff --git a/Makefile.target b/Makefile.target
index 096214a..680106f 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -328,6 +328,7 @@  else
 obj-sparc-y = sun4m.o lance.o tcx.o sun4m_iommu.o slavio_intctl.o
 obj-sparc-y += slavio_timer.o slavio_misc.o sparc32_dma.o
 obj-sparc-y += cs4231.o eccmemctl.o sbi.o sun4c_intctl.o leon3.o
+obj-sparc-y += dbri.o

 # GRLIB
 obj-sparc-y += grlib_gptimer.o grlib_irqmp.o grlib_apbuart.o
diff --git a/hw/dbri.c b/hw/dbri.c
new file mode 100644
index 0000000..46f21e4
--- /dev/null
+++ b/hw/dbri.c
@@ -0,0 +1,1342 @@ 
+/*
+ * QEMU DBRI audio interface
+ *
+ * Copyright (c) 2011 Bob Breuer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "hw.h"
+#include "audio/audio.h"
+#include "sysbus.h"
+#include "sun4m.h"
+
+/*
+ *  DBRI (Dual Basic Rate ISDN)
+ *  interface with the audio codec (CS4215) in several SPARCstation models
+ *    SS20 (internal codec), SS10 (external codec), LX, ...
+ *
+ *  Documentation at: http://www.freesoft.org/Linux/DBRI/
+ *  Linux 2.6 driver: sound/sparc/dbri.[ch]
+ *  NetBSD driver: src/sys/dev/sbus/dbri*
+ *
+ *  Unimplemented:
+ *    volume control
+ *    audio record - alaw, ulaw for record?
+ *    isdn
+ *    iommu errors (sbus faults)
+ *    monitor pipes (for 8-bit stereo?)
+ */
+
+//#define DEBUG_DBRI
+//#define DEBUG_PIPE
+//#define DEBUG_DMA
+//#define DEBUG_CODEC
+
+#ifdef DEBUG_DBRI
+#define DBRI_DPRINTF(fmt, ...)                          \
+    printf("DBRI: " fmt , ## __VA_ARGS__)
+#else
+#define DBRI_DPRINTF(fmt, ...)
+#endif
+
+#ifdef DEBUG_PIPE
+#define PIPE_DPRINTF(fmt, ...)                          \
+    printf("DBRI Pipe: " fmt , ## __VA_ARGS__)
+#else
+#define PIPE_DPRINTF(fmt, ...)
+#endif
+
+#ifdef DEBUG_DMA
+#define DMA_DPRINTF(fmt, ...)                           \
+    printf("DBRI Dma: " fmt , ## __VA_ARGS__)
+#else
+#define DMA_DPRINTF(fmt, ...)
+#endif
+
+#ifdef DEBUG_CODEC
+#define CODEC_DPRINTF(fmt, ...)                         \
+    printf("CS4215: " fmt , ## __VA_ARGS__)
+#else
+#define CODEC_DPRINTF(fmt, ...)
+#endif
+
+
+typedef struct CS4215State {
+    int data_mode;
+    int freq;
+    const int16_t *tbl;
+
+    uint8_t status;
+    uint8_t data_format;
+    uint8_t port_control;
+    uint8_t settings[4];
+} CS4215State;
+
+typedef struct DBRIPipeState {
+    uint32_t setup, ptr, data;
+    uint32_t in_desc, out_desc;
+    uint32_t in_next, out_next;
+} DBRIPipeState;
+
+typedef struct DBRIState {
+    SysBusDevice busdev;
+    QEMUSoundCard card;
+    SWVoiceOut *voice_out;
+
+    qemu_irq irq;
+    void *iommu;
+
+    uint32_t pio_default;
+    int32_t codec_offset;
+
+    uint32_t reg[2];
+    uint32_t pio;
+    uint32_t cmdq_ptr; /* REG8 */
+    uint32_t intq_ptr; /* REG9 */
+    uint32_t intq_idx;
+    uint32_t chi_global_mode;
+    uint32_t chi_data_mode;
+
+    bool pipe_update;
+    bool chi_active;
+
+    struct {
+        uint32_t pipe, ctrl;
+        int offset;
+        bool stopped;
+    } play, rec;
+
+    DBRIPipeState pipe[32];
+
+    CS4215State codec;
+} DBRIState;
+
+
+#define DBRI_ROM_SIZE   0x30
+#define DBRI_REG_SIZE   0x100
+#define DBRI_REG_OFFSET 0x10000
+
+/* bits in reg0 (status/control */
+#define DBRI_COMMAND_VALID      (1 << 15)
+#define DBRI_CHI_ACTIVATE       (1 << 4)
+#define DBRI_SOFT_RESET         (1 << 0)
+
+/* reg1 = interrupt status */
+#define DBRI_INT_STATUS         (1 << 0)
+/* reg2 = PIO (Parallel I/O)
+ * 4 bits of I/O: high nibble=enable, low nibble = value
+ *  PIO0: 1=internal codec
+ *  PIO1: 0=codec reset                 default = low
+ *  PIO2: 1=external speakerbox
+ *  PIO3: codec D/C, 1=data, 0=control  default = high
+ *
+ *  0x09 = SS-20 internal, codec offset 8
+ *  0x0c = speakerbox?, codec offset 0
+ */
+#define DBRI_PIO_EN             0xf0
+#define DBRI_PIO3               0x08
+#define DBRI_PIO2               0x04
+#define DBRI_PIO1               0x02
+#define DBRI_PIO0               0x01
+#define DBRI_PIO_DEFAULT_INTERNAL   (DBRI_PIO3 | DBRI_PIO0)
+#define DBRI_PIO_DEFAULT_EXTERNAL   (DBRI_PIO3 | DBRI_PIO2)
+#define DBRI_CODEC_RESET(s)         (((s)->pio & 0x22) != 0x22)
+#define DBRI_CODEC_DATA_MODE(s)     (((s)->pio & 0x88) != 0x80)
+
+/* interrupt codes */
+#define INTR_BRDY               1   /* Receive buffer ready */
+#define INTR_MINT               2   /* Marker interrupt in TD/RD */
+#define INTR_EOL                5   /* End of list */
+#define INTR_CMDI               6   /* Command has been read */
+#define INTR_XCMP               8   /* Transmit complete */
+#define INTR_FXDT               10  /* Fixed data change */
+#define INTR_CHIL               11  /* CHI lost frame */
+
+/* ctrl is from the TX/RX descriptors */
+#define DBRI_TXBUF_LEN(s)       (((s)->play.ctrl >> 16) & 0x1fff)
+#define DBRI_RXBUF_LEN(s)       ((s)->rec.ctrl & 0x1fff)
+
+/* pipe setup from SDP command */
+#define SDP_CHANGE              (2 << 18)  /* report any changes */
+#define SDP_EOL                 (1 << 17)
+#define SDP_MODE_MASK           (7 << 13)
+#define SDP_MODE_FIXED          (6 << 13)
+#define SDP_MODE_MEM            (0 << 13)
+#define SDP_DIR_OUT             (1 << 12)  /* direction */
+#define SDP_MSB                 (1 << 11)  /* bit order within byte */
+#define SDP_PTR_VALID           (1 << 10)
+#define SDP_ABORT               (1 << 8)
+#define SDP_CLEAR               (1 << 7)
+
+#define SDP_MODE(setup)             ((setup) & SDP_MODE_MASK)
+#define SDP_REPORT_CHANGE(setup)    ((setup) & SDP_CHANGE)
+
+/* time slot defined by DTS command */
+#define DTS_VIN                 (1 << 17)  /* valid in */
+#define DTS_VOUT                (1 << 16)  /* valid out */
+#define DTS_INSERT              (1 << 15)  /* 1=insert, 0=delete */
+#define DTS_PIPE_IN_PREV(dts)   (((dts) >> 10) & 0x1f)
+#define DTS_PIPE_OUT_PREV(dts)  (((dts) >> 5) & 0x1f)
+/* timeslot fields for in and out slots */
+#define SLOT_LEN(ts)            (((ts) >> 24) & 0xff)
+#define SLOT_START(ts)          (((ts) >> 14) & 0x3ff)
+#define SLOT_MODE(ts)           (((ts) >> 10) & 0x7)
+#define SLOT_MON(ts)            (((ts) >> 5) & 0x1f)
+#define SLOT_NEXT(ts)           ((ts) & 0x1f)
+
+/* CHI global mode from CHI command */
+#define DBRI_CHI_CLOCK(s)       (((s)->chi_global_mode >> 16) & 0xff)
+#define DBRI_CHI_IS_MASTER(s)   (DBRI_CHI_CLOCK(s) >= 3)
+
+
+/* audio codec */
+/* control slot 1 = status */
+#define CS4215_CLB              (1 << 2)  /* control latch bit */
+#define CS4215_STATUS_RWMASK    0x1f
+#define CS4215_STATUS_FIXED     0x20      /* upper 3 bits fixed at 001 */
+#define CS4215_CLB_CLEAR(st)    (((st) & 0x1b) | 0x20)
+
+/* control slot 2 = data format */
+#define CS4215_DF_MASK          (3 << 0)  /* data format */
+#define CS4215_DF_S16           (0 << 0)
+#define CS4215_DF_ULAW          (1 << 0)
+#define CS4215_DF_ALAW          (2 << 0)
+#define CS4215_DF_U8            (3 << 0)
+#define CS4215_ST               (1 << 2)  /* stereo */
+#define CS4215_DFR_MASK         (7 << 3)  /* data frequency rate */
+#define CS4215_DFR_EXTRACT(d)   (((d) & CS4215_DFR_MASK) >> 3)
+
+/* control slot 3 = port control */
+#define CS4215_XEN              (1 << 0)
+#define CS4215_XCLK             (1 << 1)  /* transmit clock master mode */
+#define CS4215_BSEL_MASK        (3 << 2)
+#define CS4215_BSEL_256         (2 << 2)
+#define CS4215_MCK_MASK         (7 << 4)  /* clock source select */
+#define CS4215_MCK_XTAL1        (1 << 4)
+#define CS4215_MCK_XTAL2        (2 << 4)
+
+#define CODEC_IS_MASTER(c)  \
+            ((c)->data_mode && ((c)->port_control & CS4215_XCLK))
+
+/* 2 clocks, 8 clock dividers */
+#define AUD_CLK1  24576000
+#define AUD_CLK2  16934400
+
+static const int cs4215_clk_div[8] = {
+    3072, 1536, 896, 768, 448, 384, 512, 2560
+};
+
+/* MuLaw/ALaw tables are also in cs4231a.c, move to common code? */
+/* Tables courtesy http://hazelware.luggle.com/tutorials/mulawcompression.html */
+static const int16_t MuLawDecompressTable[256] =
+{
+     -32124,-31100,-30076,-29052,-28028,-27004,-25980,-24956,
+     -23932,-22908,-21884,-20860,-19836,-18812,-17788,-16764,
+     -15996,-15484,-14972,-14460,-13948,-13436,-12924,-12412,
+     -11900,-11388,-10876,-10364, -9852, -9340, -8828, -8316,
+      -7932, -7676, -7420, -7164, -6908, -6652, -6396, -6140,
+      -5884, -5628, -5372, -5116, -4860, -4604, -4348, -4092,
+      -3900, -3772, -3644, -3516, -3388, -3260, -3132, -3004,
+      -2876, -2748, -2620, -2492, -2364, -2236, -2108, -1980,
+      -1884, -1820, -1756, -1692, -1628, -1564, -1500, -1436,
+      -1372, -1308, -1244, -1180, -1116, -1052,  -988,  -924,
+       -876,  -844,  -812,  -780,  -748,  -716,  -684,  -652,
+       -620,  -588,  -556,  -524,  -492,  -460,  -428,  -396,
+       -372,  -356,  -340,  -324,  -308,  -292,  -276,  -260,
+       -244,  -228,  -212,  -196,  -180,  -164,  -148,  -132,
+       -120,  -112,  -104,   -96,   -88,   -80,   -72,   -64,
+        -56,   -48,   -40,   -32,   -24,   -16,    -8,     0,
+      32124, 31100, 30076, 29052, 28028, 27004, 25980, 24956,
+      23932, 22908, 21884, 20860, 19836, 18812, 17788, 16764,
+      15996, 15484, 14972, 14460, 13948, 13436, 12924, 12412,
+      11900, 11388, 10876, 10364,  9852,  9340,  8828,  8316,
+       7932,  7676,  7420,  7164,  6908,  6652,  6396,  6140,
+       5884,  5628,  5372,  5116,  4860,  4604,  4348,  4092,
+       3900,  3772,  3644,  3516,  3388,  3260,  3132,  3004,
+       2876,  2748,  2620,  2492,  2364,  2236,  2108,  1980,
+       1884,  1820,  1756,  1692,  1628,  1564,  1500,  1436,
+       1372,  1308,  1244,  1180,  1116,  1052,   988,   924,
+        876,   844,   812,   780,   748,   716,   684,   652,
+        620,   588,   556,   524,   492,   460,   428,   396,
+        372,   356,   340,   324,   308,   292,   276,   260,
+        244,   228,   212,   196,   180,   164,   148,   132,
+        120,   112,   104,    96,    88,    80,    72,    64,
+         56,    48,    40,    32,    24,    16,     8,     0
+};
+
+static const int16_t ALawDecompressTable[256] =
+{
+     -5504, -5248, -6016, -5760, -4480, -4224, -4992, -4736,
+     -7552, -7296, -8064, -7808, -6528, -6272, -7040, -6784,
+     -2752, -2624, -3008, -2880, -2240, -2112, -2496, -2368,
+     -3776, -3648, -4032, -3904, -3264, -3136, -3520, -3392,
+     -22016,-20992,-24064,-23040,-17920,-16896,-19968,-18944,
+     -30208,-29184,-32256,-31232,-26112,-25088,-28160,-27136,
+     -11008,-10496,-12032,-11520,-8960, -8448, -9984, -9472,
+     -15104,-14592,-16128,-15616,-13056,-12544,-14080,-13568,
+     -344,  -328,  -376,  -360,  -280,  -264,  -312,  -296,
+     -472,  -456,  -504,  -488,  -408,  -392,  -440,  -424,
+     -88,   -72,   -120,  -104,  -24,   -8,    -56,   -40,
+     -216,  -200,  -248,  -232,  -152,  -136,  -184,  -168,
+     -1376, -1312, -1504, -1440, -1120, -1056, -1248, -1184,
+     -1888, -1824, -2016, -1952, -1632, -1568, -1760, -1696,
+     -688,  -656,  -752,  -720,  -560,  -528,  -624,  -592,
+     -944,  -912,  -1008, -976,  -816,  -784,  -880,  -848,
+      5504,  5248,  6016,  5760,  4480,  4224,  4992,  4736,
+      7552,  7296,  8064,  7808,  6528,  6272,  7040,  6784,
+      2752,  2624,  3008,  2880,  2240,  2112,  2496,  2368,
+      3776,  3648,  4032,  3904,  3264,  3136,  3520,  3392,
+      22016, 20992, 24064, 23040, 17920, 16896, 19968, 18944,
+      30208, 29184, 32256, 31232, 26112, 25088, 28160, 27136,
+      11008, 10496, 12032, 11520, 8960,  8448,  9984,  9472,
+      15104, 14592, 16128, 15616, 13056, 12544, 14080, 13568,
+      344,   328,   376,   360,   280,   264,   312,   296,
+      472,   456,   504,   488,   408,   392,   440,   424,
+      88,    72,   120,   104,    24,     8,    56,    40,
+      216,   200,   248,   232,   152,   136,   184,   168,
+      1376,  1312,  1504,  1440,  1120,  1056,  1248,  1184,
+      1888,  1824,  2016,  1952,  1632,  1568,  1760,  1696,
+      688,   656,   752,   720,   560,   528,   624,   592,
+      944,   912,  1008,   976,   816,   784,   880,   848
+};
+
+/* reverse bits in a byte */
+static uint8_t byte_rev(uint8_t b)
+{
+    b = ((b & 0xf0) >> 4) | ((b & 0x0f) << 4);
+    b = ((b & 0xcc) >> 2) | ((b & 0x33) << 2);
+    b = ((b & 0xaa) >> 1) | ((b & 0x55) << 1);
+
+    return b;
+}
+
+static void cs4215_reset(CS4215State *s)
+{
+    s->status = CS4215_STATUS_FIXED | CS4215_CLB;
+    s->data_format = CS4215_DF_ULAW;
+    s->port_control = CS4215_BSEL_256 | CS4215_XEN;
+    s->settings[0] = 0x3f;
+    s->settings[1] = 0xbf;
+    s->settings[2] = 0xc0;
+    s->settings[3] = 0xf0;
+}
+
+static void cs4215_setmode(CS4215State *s, int mode)
+{
+    int div;
+
+    s->data_mode = mode;
+
+    if (mode) {
+        /* calculate frequency */
+        div = cs4215_clk_div[CS4215_DFR_EXTRACT(s->data_format)];
+
+        switch (s->port_control & CS4215_MCK_MASK) {
+        case CS4215_MCK_XTAL1:
+            s->freq = AUD_CLK1 / div;
+            break;
+        case CS4215_MCK_XTAL2:
+            s->freq = AUD_CLK2 / div;
+            break;
+        default:
+            s->freq = 0;
+            break;
+        }
+    } else {
+        s->freq = 0;
+        s->status = CS4215_CLB_CLEAR(s->status);
+    }
+}
+
+static void cs4215_getformat(CS4215State *s, struct audsettings *as)
+{
+    as->freq = s->freq;
+    as->nchannels = (s->data_format & CS4215_ST) ? 2 : 1;
+    as->endianness = AUDIO_HOST_ENDIANNESS;
+
+    CODEC_DPRINTF("audio format: %d Hz, %d chan, fmt %d\n",
+        as->freq, as->nchannels, s->data_format & CS4215_DF_MASK);
+
+    s->tbl = NULL; /* conversion table */
+
+    switch (s->data_format & CS4215_DF_MASK) {
+    case CS4215_DF_S16: /* 16-bit 2's complement */
+        as->fmt = AUD_FMT_S16;
+        as->endianness = 1;   /* big-endian */
+        break;
+    case CS4215_DF_ULAW: /* 8-bit Mu-law */
+        s->tbl = MuLawDecompressTable;
+        as->fmt = AUD_FMT_S16;
+        break;
+    case CS4215_DF_ALAW: /* 8-bit A-law */
+        s->tbl = ALawDecompressTable;
+        as->fmt = AUD_FMT_S16;
+        break;
+    case CS4215_DF_U8: /* 8-bit unsigned */
+        as->fmt = AUD_FMT_U8;
+        break;
+    }
+}
+
+static uint8_t cs4215_read(CS4215State *s, int slot)
+{
+    uint8_t val;
+
+    /* 1-based slot number */
+    if (s->data_mode) {
+        switch (slot) {
+        case 5:
+            val = s->settings[0];
+            break;
+        case 6:
+            val = s->settings[1] & 0x7f;
+            break;
+        case 7:
+            val = s->settings[2] & 0xdf;
+            break;
+        case 8:
+            val = s->settings[3];
+            break;
+        default:
+            val = 0;
+            break;
+        }
+    } else {
+        switch (slot) {
+        case 1:
+            val = s->status;
+            break;
+        case 2:
+            val = s->data_format;
+            break;
+        case 3:
+            val = s->port_control;
+            break;
+        case 4:
+            val = 0; /* test */
+            break;
+        case 5:
+            val = 0xc0; /* codec pio */
+            break;
+        case 7:
+            val = 0x02; /* Rev E */
+            break;
+        default:
+            val = 0;
+            break;
+        }
+    }
+    CODEC_DPRINTF("read 0x%02x from %s slot %d\n",
+        val, s->data_mode ? "data" : "control", slot);
+    return val;
+}
+
+static void cs4215_write(CS4215State *s, int slot, uint8_t val)
+{
+    CODEC_DPRINTF("write 0x%02x to %s slot %d\n",
+        val, s->data_mode ? "data" : "control", slot);
+
+    if (s->data_mode) {
+        if (slot >= 5 && slot <= 8) {
+            s->settings[slot-5] = val;
+        }
+    } else {
+        switch (slot) {
+        case 1:
+            s->status = (val & CS4215_STATUS_RWMASK) | CS4215_STATUS_FIXED;
+            break;
+        case 2:
+            s->data_format = val;
+            break;
+        case 3:
+            s->port_control = val;
+            break;
+        }
+    }
+}
+
+static uint32_t dbri_dma_readl(DBRIState *s, uint32_t addr)
+{
+    uint32_t val;
+
+    sparc_iommu_memory_read(s->iommu, addr, (uint8_t *)&val, 4);
+    val = be32_to_cpu(val);
+    DMA_DPRINTF("readl 0x%08x\n", val);
+    return val;
+}
+
+static void dbri_dma_writel(DBRIState *s, uint32_t addr, uint32_t val)
+{
+    DMA_DPRINTF("writel 0x%08x\n", val);
+    val = cpu_to_be32(val);
+    sparc_iommu_memory_write(s->iommu, addr, (uint8_t *)&val, 4);
+}
+
+#ifdef DEBUG_DBRI
+static const char *intr_code[16] = {
+    "code 0", "BRDY", "MINT", "IBEG", "IEND", "EOL", "CMDI", "code 7",
+    "XCMP", "SBRI", "FXDT", "CHIL/COLL", "DBYT", "RBYT", "LINT", "UNDR"
+};
+#endif
+
+static void dbri_post_interrupt(DBRIState *s, int chan, int code, int field)
+{
+    uint32_t addr, val;
+
+    addr = s->intq_ptr;
+    if (!addr) {
+        /* disabled */
+        return;
+    }
+
+    if (s->intq_idx == 64) {
+        /* read next interrupt block from 1st word */
+        addr = dbri_dma_readl(s, addr);
+        if (!addr) {
+            /* out of space? */
+            return;
+        }
+        s->intq_ptr = addr;
+        s->intq_idx = 1;
+    }
+
+    field &= 0xfffff;
+    val = 0x80000000 | (chan << 24) | (code << 20) | field;
+
+    DBRI_DPRINTF("interrupt (0x%08x) %s, chan %d, field 0x%05x\n",
+        val, intr_code[code], chan, field);
+
+    addr += 4 * s->intq_idx;
+    s->intq_idx++;
+
+    dbri_dma_writel(s, addr, val);
+    s->reg[1] |= DBRI_INT_STATUS;
+    qemu_irq_raise(s->irq);
+}
+
+static void dbri_out_cb(void *opaque, int free)
+{
+    DBRIState *s = opaque;
+    int pipe = s->play.pipe;
+    int left, used;
+    int len, buf_len;
+    uint32_t buf_ptr;
+    uint8_t tmpbuf[1024];
+    int16_t linbuf[1024];
+
+    if (!pipe || s->play.stopped) {
+        AUD_set_active_out(s->voice_out, 0);
+        return;
+    }
+
+    used = 0;
+    while (free) {
+        buf_len = DBRI_TXBUF_LEN(s);
+        left = buf_len - s->play.offset;
+
+        /* end of buffer? */
+        if (left <= 0) {
+            uint32_t td_ptr = s->pipe[pipe].ptr;
+
+            if (!td_ptr) {
+                /* bad descriptor pointer - flag a fault? */
+                s->play.stopped = 1;
+                break;
+            }
+
+            if (s->pipe[pipe].data) {
+                /* clear our data pointer to flag buffer done */
+                s->pipe[pipe].data = 0;
+
+                /* update status */
+                dbri_dma_writel(s, td_ptr+12, 0x01);
+                if ((s->play.ctrl & 0x80008000) == 0x80008000) {
+                    /* transmit complete */
+                    dbri_post_interrupt(s, pipe, INTR_XCMP, 0);
+                }
+            }
+            /* next descriptor */
+            td_ptr = dbri_dma_readl(s, td_ptr+8);
+            if (!td_ptr) {
+                DBRI_DPRINTF("out of tx data after %d bytes\n", used);
+                if (used) {
+                    /*
+                     * maybe the host buffers are too big,
+                     * give the guest a chance to catch up
+                     */
+                    break;
+                }
+                if (s->pipe[pipe].setup & SDP_EOL) {
+                    /* end of list */
+                    dbri_post_interrupt(s, pipe, INTR_EOL, td_ptr);
+                }
+                s->play.stopped = 1;
+                break;
+            }
+            s->pipe[pipe].ptr = td_ptr;
+            s->play.ctrl = dbri_dma_readl(s, td_ptr);
+            if (s->play.ctrl & 0x4000) {
+                /* marker int */
+                dbri_post_interrupt(s, pipe, INTR_MINT, td_ptr);
+            }
+            /* setup next buffer */
+            s->pipe[pipe].data = dbri_dma_readl(s, td_ptr+4);
+            s->play.offset = 0;
+            if (!s->pipe[pipe].data) {
+                /* NULL data pointer, what now? */
+            }
+            left = buf_len = DBRI_TXBUF_LEN(s);
+
+            DBRI_DPRINTF("play buffer @ 0x%08x, len %d\n",
+                s->pipe[pipe].data, buf_len);
+        }
+
+        if (!s->pipe[pipe].data) {
+            /* bad data pointer - flag a fault? */
+            s->play.stopped = 1;
+            break;
+        }
+        buf_ptr = s->pipe[pipe].data + s->play.offset;
+        len = audio_MIN(left, sizeof(tmpbuf));
+
+        if (s->codec.tbl) {
+            /* convert from A/Mu-Law to 16-bit linear */
+            const int16_t *tbl;
+            int i;
+
+            len = audio_MIN(len, free >> 1);
+
+            sparc_iommu_memory_read(s->iommu, buf_ptr, tmpbuf, len);
+
+            tbl = s->codec.tbl;
+            for (i = 0; i < len; i++) {
+                linbuf[i] = tbl[tmpbuf[i]];
+            }
+            len = AUD_write(s->voice_out, linbuf, len << 1);
+            len >>= 1;
+        } else {
+            len = audio_MIN(len, free);
+
+            sparc_iommu_memory_read(s->iommu, buf_ptr, tmpbuf, len);
+
+            len = AUD_write(s->voice_out, tmpbuf, len);
+        }
+
+        if (!len) {
+            break;
+        }
+
+        s->play.offset += len;
+        free -= len;
+        used += len;
+    }
+}
+
+static void dbri_update_audio(DBRIState *s)
+{
+    struct audsettings as;
+
+    cs4215_getformat(&s->codec, &as);
+    s->voice_out = AUD_open_out(&s->card, s->voice_out,
+                                "dbri_out", s, dbri_out_cb, &as);
+
+    /* zero is not a valid pipe */
+    if (s->play.pipe && !s->play.stopped) {
+        AUD_set_active_out(s->voice_out, 1);
+    } else {
+        AUD_set_active_out(s->voice_out, 0);
+    }
+}
+
+static void dbri_start_audio_out(DBRIState *s, int pipe)
+{
+    uint32_t td_ptr;  /* transmit descriptor */
+
+    if (pipe == s->play.pipe && !s->play.stopped) {
+        return;
+    }
+    s->play.pipe = pipe;
+
+    /* ptr was validated by caller */
+    td_ptr = s->pipe[pipe].ptr;
+
+    s->play.ctrl = dbri_dma_readl(s, td_ptr);   /* control/length */
+    if (s->play.ctrl & 0x4000) {
+        /* marker int */
+        dbri_post_interrupt(s, pipe, INTR_MINT, td_ptr);
+    }
+    /* get data pointer, re-use pipe data field */
+    s->pipe[pipe].data = dbri_dma_readl(s, td_ptr+4);
+
+    /* prepare buffer */
+    s->play.offset = 0;
+    s->play.stopped = 0;
+
+    DBRI_DPRINTF("play buffer @ 0x%08x, len %d\n",
+        s->pipe[pipe].data, DBRI_TXBUF_LEN(s));
+
+    dbri_update_audio(s);
+}
+
+static void dbri_start_audio_in(DBRIState *s, int pipe)
+{
+    uint32_t rd_ptr;  /* receive descriptor */
+
+    if (pipe == s->rec.pipe && !s->rec.stopped) {
+        return;
+    }
+    s->rec.pipe = pipe;
+
+    /* setup format */
+
+    rd_ptr = s->pipe[pipe].ptr;
+
+    s->rec.ctrl = dbri_dma_readl(s, rd_ptr+12); /* control/length */
+    if (s->rec.ctrl & 0x4000) {
+        /* marker int */
+        dbri_post_interrupt(s, pipe, INTR_MINT, rd_ptr);
+    }
+    /* get data pointer, re-use pipe data field */
+    s->pipe[pipe].data = dbri_dma_readl(s, rd_ptr+4);
+
+    /* prepare buffer */
+    s->rec.offset = 0;
+    s->rec.stopped = 0;
+
+    /* TODO: audio record, this is just a placeholder */
+    if (0) {
+        /* write buffer */
+        uint32_t len = DBRI_RXBUF_LEN(s);
+
+        /* update status - completed and EOF */
+        dbri_dma_writel(s, rd_ptr, 0xc0000000 | (len << 16));
+        if (s->rec.ctrl & 0x8000) {
+            /* buffer ready */
+            dbri_post_interrupt(s, pipe, INTR_BRDY, rd_ptr);
+        }
+        /* next */
+        rd_ptr = dbri_dma_readl(s, rd_ptr+8);
+        s->pipe[pipe].ptr = rd_ptr;
+    }
+    if (s->pipe[pipe].setup & SDP_EOL) {
+        /* end of list */
+        dbri_post_interrupt(s, pipe, INTR_EOL, rd_ptr);
+    }
+    s->rec.stopped = 1;
+    dbri_update_audio(s);
+}
+
+static void dbri_stop_audio(DBRIState *s, int pipe, int abort)
+{
+    if (pipe == s->play.pipe) {
+        if (abort && s->pipe[pipe].ptr) {
+            /* abort */
+            dbri_dma_writel(s, s->pipe[pipe].ptr+12, 0x04);
+            AUD_set_active_out(s->voice_out, 0);
+        }
+        s->play.stopped = 1;
+        s->play.pipe = 0;
+    }
+    if (pipe == s->rec.pipe) {
+        if (abort && s->pipe[pipe].ptr) {
+            /* abort */
+            dbri_dma_writel(s, s->pipe[pipe].ptr, 0x20);
+        }
+        s->rec.stopped = 1;
+        s->rec.pipe = 0;
+    }
+}
+
+static void dbri_update_chi_status(DBRIState *s)
+{
+    if ((s->reg[0] & DBRI_CHI_ACTIVATE)
+        && (DBRI_CHI_IS_MASTER(s) || CODEC_IS_MASTER(&s->codec))) {
+        s->chi_active = 1;
+    } else {
+        s->chi_active = 0;
+    }
+}
+
+static void dbri_run_pipes(DBRIState *s)
+{
+    int i, start, len, codec_slot;
+    uint32_t val;
+    uint32_t been_here;
+
+    if (!s->chi_active) {
+        return; /* CHI not active */
+    }
+
+    /* run through the pipes */
+    s->pipe_update = 0;
+
+    /* pipe 16 is the anchor where the CHI starts and ends */
+    if (s->chi_data_mode & 0x02) {
+        i = s->pipe[16].out_next;
+        been_here = 1 << 16;
+        PIPE_DPRINTF("OUT pipes:\n");
+        while (i != 16) {
+            if (been_here & (1 << i)) {
+                PIPE_DPRINTF("linked list loop before OUT anchor\n");
+                break;
+            }
+            been_here |= 1 << i;
+            start = SLOT_START(s->pipe[i].out_desc);
+            len = SLOT_LEN(s->pipe[i].out_desc);
+
+            /* use bits per frame instead of 0 for OUT pipes */
+            if (start >= 64) {
+                start = 0;
+            }
+            PIPE_DPRINTF(" %d, 0x%08x = start %d, len %d\n",
+                i, s->pipe[i].out_desc, start, len);
+
+            switch (SDP_MODE(s->pipe[i].setup)) {
+            case SDP_MODE_MEM:
+                PIPE_DPRINTF("  TD @ 0x%08x\n", s->pipe[i].ptr);
+
+                if (s->pipe[i].ptr && start == s->codec_offset) {
+                    dbri_start_audio_out(s, i);
+                }
+                break;
+
+            case SDP_MODE_FIXED:
+                PIPE_DPRINTF("  Fixed 0x%08x\n", s->pipe[i].data);
+
+                /* assume 8-bit alignment */
+                codec_slot = 1 + (start - s->codec_offset) / 8;
+                /* fixed pipe is LSB first, codec is MSB first */
+                val = s->pipe[i].data;
+                do {
+                    cs4215_write(&s->codec, codec_slot, byte_rev(val & 0xff));
+                    codec_slot++;
+                    val >>= 8;
+                    len -= 8;
+                } while (len > 0);
+                break;
+            }
+            i = s->pipe[i].out_next;
+        }
+    }
+
+    i = s->pipe[16].in_next;
+    been_here = 1 << 16;
+    PIPE_DPRINTF("IN pipes:\n");
+    while (i != 16) {
+        if (been_here & (1 << i)) {
+            PIPE_DPRINTF("linked list loop before IN anchor\n");
+            break;
+        }
+        been_here |= 1 << i;
+        start = SLOT_START(s->pipe[i].in_desc);
+        len = SLOT_LEN(s->pipe[i].in_desc);
+
+        PIPE_DPRINTF(" %d, 0x%08x = start %d, len %d\n",
+            i, s->pipe[i].in_desc, start, len);
+
+        switch (SDP_MODE(s->pipe[i].setup)) {
+        case SDP_MODE_MEM:
+            PIPE_DPRINTF("  RD @ 0x%08x\n", s->pipe[i].ptr);
+
+            if (s->pipe[i].ptr && start == s->codec_offset) {
+                dbri_start_audio_in(s, i);
+            }
+            break;
+
+        case SDP_MODE_FIXED:
+            /* assume 8-bit alignment */
+            codec_slot = 1 + (start - s->codec_offset) / 8;
+            /* fixed pipe is LSB first, codec is MSB first */
+            val = byte_rev(cs4215_read(&s->codec, codec_slot));
+            if (len > 8) {
+                val |= byte_rev(cs4215_read(&s->codec, codec_slot+1)) << 8;
+            }
+            if (s->pipe[i].data != val) {
+                s->pipe[i].data = val;
+                if (SDP_REPORT_CHANGE(s->pipe[i].setup)) {
+                    dbri_post_interrupt(s, i, INTR_FXDT, val);
+                }
+            }
+            PIPE_DPRINTF("  Fixed 0x%08x\n", s->pipe[i].data);
+            break;
+        }
+        i = s->pipe[i].in_next;
+    }
+}
+
+static void dbri_cmd_pause(DBRIState *s, uint32_t *cmd)
+{
+    dbri_run_pipes(s);
+}
+
+static void dbri_cmd_jump(DBRIState *s, uint32_t *cmd)
+{
+    s->cmdq_ptr = cmd[1];
+}
+
+/* initialize interrupt queue */
+static void dbri_cmd_iiq(DBRIState *s, uint32_t *cmd)
+{
+    s->intq_ptr = cmd[1];
+    s->intq_idx = 1;
+}
+
+/* setup data pipe, set data pointer */
+static void dbri_cmd_sdp(DBRIState *s, uint32_t *cmd)
+{
+    int i = cmd[0] & 0x1f;
+
+    PIPE_DPRINTF("Setup pipe %d for %s: mode %d, IRM 0x%x, clear %d\n",
+        i, (cmd[0] & SDP_DIR_OUT) ? "output" : "input",
+        (cmd[0] >> 13) & 7, (cmd[0] >> 16) & 0xf,
+        (cmd[0] >> 7) & 1);
+
+    s->pipe[i].setup = cmd[0];
+    if (cmd[0] & (SDP_PTR_VALID | SDP_CLEAR | SDP_ABORT)) {
+        /* stop any audio on this pipe */
+        if (i) {
+            dbri_stop_audio(s, i, cmd[0] & SDP_ABORT);
+        }
+    }
+    if (cmd[0] & SDP_PTR_VALID) {
+        PIPE_DPRINTF(" pipe pointer = 0x%08x\n", cmd[1]);
+        s->pipe[i].ptr = cmd[1];
+    }
+}
+
+/* continue data pipe */
+static void dbri_cmd_cdp(DBRIState *s, uint32_t *cmd)
+{
+    int i = cmd[0] & 0x1f;
+
+    if (i == s->play.pipe) {
+        s->play.stopped = 0;
+    }
+    if (i == s->rec.pipe) {
+        s->rec.stopped = 0;
+    }
+    dbri_update_audio(s);
+}
+
+/* define time slot */
+static void dbri_cmd_dts(DBRIState *s, uint32_t *cmd)
+{
+    int prev, next;
+    int i = cmd[0] & 0x1f;
+
+    PIPE_DPRINTF("%s time slots for pipe %d\n",
+        (cmd[0] & DTS_INSERT) ? "add/modify" : "delete", i);
+
+    if (cmd[0] & DTS_VIN) {
+        prev = DTS_PIPE_IN_PREV(cmd[0]);
+        if (cmd[0] & DTS_INSERT) {
+            next = SLOT_NEXT(cmd[1]);
+
+            s->pipe[i].in_next = next;
+            s->pipe[prev].in_next = i;
+            s->pipe[i].in_desc = cmd[1];
+        } else {
+            /* delete */
+            next = s->pipe[i].in_next;
+            s->pipe[prev].in_next = next;
+        }
+
+        PIPE_DPRINTF("In:  prev=%d, next=%d, mode=%d, len=%d, cycle=%d\n",
+            prev, next, SLOT_MODE(cmd[1]),
+            SLOT_LEN(cmd[1]), SLOT_START(cmd[1]));
+    }
+    if (cmd[0] & DTS_VOUT) {
+        prev = DTS_PIPE_OUT_PREV(cmd[0]);
+        if (cmd[0] & DTS_INSERT) {
+            next = SLOT_NEXT(cmd[2]);
+
+            s->pipe[i].out_next = next;
+            s->pipe[prev].out_next = i;
+            s->pipe[i].out_desc = cmd[2];
+        } else {
+            /* delete */
+            next = s->pipe[i].out_next;
+            s->pipe[prev].out_next = next;
+        }
+
+        PIPE_DPRINTF("Out: prev=%d, next=%d, mode=%d, len=%d, cycle=%d\n",
+            prev, next, SLOT_MODE(cmd[2]),
+            SLOT_LEN(cmd[2]), SLOT_START(cmd[2]));
+    }
+}
+
+/* set short pipe data */
+static void dbri_cmd_ssp(DBRIState *s, uint32_t *cmd)
+{
+    unsigned int i = cmd[0] & 0x1f;
+
+    /* short pipe only */
+    if (i > 16) {
+        s->pipe[i].data = cmd[1];
+    }
+}
+
+/* set CHI global mode */
+static void dbri_cmd_chi(DBRIState *s, uint32_t *cmd)
+{
+    int active;
+    uint32_t status;
+
+    active = DBRI_CHI_IS_MASTER(s) || CODEC_IS_MASTER(&s->codec);
+    s->chi_global_mode = cmd[0];
+
+    if (s->chi_global_mode & 0x8000) {
+        /* report status */
+        status = s->chi_data_mode & 0x03;
+        if (!active) {
+            status |= 0x04;
+        }
+        dbri_post_interrupt(s, 36, INTR_CHIL, status);
+    }
+    dbri_update_chi_status(s);
+}
+
+/* set CHI data mode */
+static void dbri_cmd_cdm(DBRIState *s, uint32_t *cmd)
+{
+    s->chi_data_mode = cmd[0];
+}
+
+static const struct {
+    const char *name;
+    int len;
+    void(*action)(DBRIState *, uint32_t *);
+} command_list[16] = {
+    { "WAIT",  0, NULL              },
+    { "PAUSE", 4, dbri_cmd_pause    },
+    { "JUMP",  4, dbri_cmd_jump     },
+    { "IIQ",   8, dbri_cmd_iiq      },  /* Initialize Interrupt Queue */
+    { "REX",   4, NULL              },  /* Report command EXecution   */
+    { "SDP",   8, dbri_cmd_sdp      },  /* Setup Data Pipe            */
+    { "CDP",   4, dbri_cmd_cdp      },  /* Continue Data Pipe         */
+    { "DTS",  12, dbri_cmd_dts      },  /* Define Time Slot           */
+    { "SSP",   8, dbri_cmd_ssp      },  /* Set Short Pipe             */
+    { "CHI",   4, dbri_cmd_chi      },  /* Set CHI Global Mode        */
+    { "NT",    4, NULL              },
+    { "TE",    4, NULL              },
+    { "CDEC",  4, NULL              },  /* Codec Setup                */
+    { "TEST", 12, NULL              },
+    { "CDM",   4, dbri_cmd_cdm      },  /* Set CHI Data Mode          */
+    { "Reserved", -1, NULL          }
+};
+
+static void dbri_run_commands(DBRIState *s)
+{
+    uint32_t cmd[3];
+    uint32_t val;
+    int stopped, i;
+
+    for (stopped = 0; !stopped; ) {
+        cmd[0] = dbri_dma_readl(s, s->cmdq_ptr);
+        i = cmd[0] >> 28;
+
+        /* interrupt on command? */
+        if (cmd[0] & (1<<27)) {
+            val = (i << 16) | (cmd[0] & 0xffff);
+            dbri_post_interrupt(s, 38, INTR_CMDI, val);
+        }
+
+        DBRI_DPRINTF("cmd %s = 0x%08x\n", command_list[i].name, cmd[0]);
+
+        switch (command_list[i].len) {
+        case 12:
+            cmd[2] = dbri_dma_readl(s, s->cmdq_ptr+8);
+        case 8:
+            cmd[1] = dbri_dma_readl(s, s->cmdq_ptr+4);
+        case 4:
+            s->cmdq_ptr += command_list[i].len;
+            s->pipe_update = 1;
+            if (command_list[i].action) {
+                command_list[i].action(s, cmd);
+            }
+            break;
+        case 0:
+        default:
+            stopped = 1;
+            s->reg[0] &= ~DBRI_COMMAND_VALID;
+            if (s->pipe_update) {
+                dbri_run_pipes(s);
+            }
+            break;
+        }
+    }
+}
+
+static void dbri_reset(DeviceState *dev)
+{
+    DBRIState *s = container_of(dev, DBRIState, busdev.qdev);
+    int i;
+
+    AUD_set_active_out(s->voice_out, 0);
+    qemu_irq_lower(s->irq);
+
+    /* set defaults */
+    s->reg[0] = 0x4008;
+    s->reg[1] = 0;
+    if (s->codec_offset) {
+        s->pio_default = DBRI_PIO_DEFAULT_INTERNAL;
+    } else {
+        s->pio_default = DBRI_PIO_DEFAULT_EXTERNAL;
+    }
+    s->pio = s->pio_default;
+    /* reset pointers */
+    s->cmdq_ptr = 0;
+    s->intq_ptr = 0;
+
+    s->chi_global_mode = 0;
+    s->chi_data_mode = 0;
+    s->chi_active = 0;
+
+    /* reset linked list */
+    s->pipe[16].in_next = 16;
+    s->pipe[16].out_next = 16;
+
+    /* clear all pipes */
+    s->pipe_update = 0;
+    for (i = 0; i < 32; i++) {
+        s->pipe[i].setup = 0;
+        s->pipe[i].ptr = 0;
+        s->pipe[i].data = 0;
+    }
+    s->play.pipe = 0;
+    s->rec.pipe = 0;
+    s->play.stopped = 1;
+    s->rec.stopped = 1;
+
+    cs4215_reset(&s->codec);
+    cs4215_setmode(&s->codec, DBRI_CODEC_DATA_MODE(s));
+}
+
+/* registers */
+static uint32_t dbri_reg_readl(void *opaque, target_phys_addr_t addr)
+{
+    DBRIState *s = opaque;
+    int val;
+
+    switch (addr) {
+    case 0x00: /* Status and Control */
+        val = s->reg[0];
+        break;
+    case 0x04: /* Mode and Interrupt */
+        val = s->reg[1];
+        if (val) {
+            /* clear interrupt status */
+            s->reg[1] = 0;
+            qemu_irq_lower(s->irq);
+        }
+        break;
+    case 0x08: /* I/O */
+        val = s->pio;
+        break;
+    case 0x20: /* Command Queue Pointer */
+        val = s->cmdq_ptr;
+        break;
+    case 0x24: /* Interrupt Queue Pointer */
+        val = s->intq_ptr;
+        break;
+    default:
+        val = 0;
+        break;
+    }
+
+    DBRI_DPRINTF("readl 0x%08x from reg " TARGET_FMT_plx "\n", val, addr);
+
+    return val;
+}
+
+static void dbri_reg_writel(void *opaque, target_phys_addr_t addr, uint32_t val)
+{
+    DBRIState *s = opaque;
+
+    DBRI_DPRINTF("writel 0x%08x to reg " TARGET_FMT_plx "\n", val, addr);
+
+    switch (addr) {
+    case 0x00: /* Status and Control */
+        s->reg[0] = val;
+        if (val & DBRI_SOFT_RESET) {
+            dbri_reset(&s->busdev.qdev);
+        } else {
+            dbri_update_chi_status(s);
+            if (val & (DBRI_COMMAND_VALID | DBRI_CHI_ACTIVATE)) {
+                dbri_run_commands(s);
+            }
+        }
+        break;
+    case 0x08: /* I/O */
+        s->pio = (val & DBRI_PIO_EN) ? val : s->pio_default;
+        if (DBRI_CODEC_RESET(s)) {
+            cs4215_reset(&s->codec);
+        }
+        cs4215_setmode(&s->codec, DBRI_CODEC_DATA_MODE(s));
+        dbri_update_chi_status(s);
+        break;
+    case 0x20: /* Command Queue Pointer */
+        s->cmdq_ptr = val;
+        s->reg[0] |= DBRI_COMMAND_VALID;
+        dbri_run_commands(s);
+        break;
+    default:
+        break;
+    }
+}
+
+static CPUReadMemoryFunc * const dbri_reg_read[3] = {
+    NULL,
+    NULL,
+    dbri_reg_readl,
+};
+
+static CPUWriteMemoryFunc * const dbri_reg_write[3] = {
+    NULL,
+    NULL,
+    dbri_reg_writel,
+};
+
+static int vmstate_dbri_post_load(void *opaque, int version_id)
+{
+    DBRIState *s = opaque;
+
+    cs4215_setmode(&s->codec, DBRI_CODEC_DATA_MODE(s));
+    dbri_update_chi_status(s);
+
+    if (s->intq_ptr && s->reg[1]) {
+        qemu_irq_raise(s->irq);
+    }
+
+    /* resume playback */
+    dbri_update_audio(s);
+
+    return 0;
+}
+
+
+/* the FCode rom */
+static const uint8_t dbri_rom[DBRI_ROM_SIZE] = {
+    0xfd, 0x00, 0x09, 0xea, 0x00, 0x00, 0x00, 0x30, 0x12, 0x0a,
+    'S',  'U',  'N',  'W',  ',',  'D',  'B',  'R',  'I',  'e',
+    0x01, 0x14, 0x12, 0x04, 'n',  'a',  'm',  'e',  0x01, 0x10,
+    0x01, 0x02, 0xa5, 0xa6, 0x7d, 0x1e, 0x01, 0x03, 0xa6, 0x80,
+    0x01, 0x16, 0xa8, 0x63, 0xa5, 0x01, 0x17, 0x00
+};
+
+/* everything is an offset within our sbus slot */
+static void dbri_sbus_map(SysBusDevice *dev, target_phys_addr_t base)
+{
+    DBRIState *s = FROM_SYSBUS(DBRIState, dev);
+    int rom, regs;
+
+    rom = qemu_ram_alloc(NULL, "dbri.rom", DBRI_ROM_SIZE);
+    /* the rom is at offset 0 */
+    cpu_register_physical_memory(base, DBRI_ROM_SIZE, rom|IO_MEM_ROM);
+    cpu_physical_memory_write_rom(base, dbri_rom, DBRI_ROM_SIZE);
+
+    /* mirror at 0x1000, where the SS-20 bootrom looks for it */
+    cpu_register_physical_memory(base+0x1000, DBRI_ROM_SIZE, rom|IO_MEM_ROM);
+
+    regs = cpu_register_io_memory(dbri_reg_read, dbri_reg_write, s,
+        DEVICE_NATIVE_ENDIAN);
+    cpu_register_physical_memory(base+DBRI_REG_OFFSET, DBRI_REG_SIZE, regs);
+}
+
+static int dbri_init1(SysBusDevice *dev)
+{
+    DBRIState *s = FROM_SYSBUS(DBRIState, dev);
+
+    sysbus_init_mmio_cb(dev, DBRI_REG_OFFSET+DBRI_REG_SIZE, dbri_sbus_map);
+    sysbus_init_irq(dev, &s->irq);
+
+    AUD_register_card("sun_dbri", &s->card);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_dbri_pipe = {
+    .name = "dbri_pipe",
+    .version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(setup,    DBRIPipeState),
+        VMSTATE_UINT32(ptr,      DBRIPipeState),
+        VMSTATE_UINT32(data,     DBRIPipeState),
+        VMSTATE_UINT32(in_desc,  DBRIPipeState),
+        VMSTATE_UINT32(out_desc, DBRIPipeState),
+        VMSTATE_UINT32(in_next,  DBRIPipeState),
+        VMSTATE_UINT32(out_next, DBRIPipeState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const VMStateDescription vmstate_dbri = {
+    .name = "dbri",
+    .version_id = 1,
+    .post_load = vmstate_dbri_post_load,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(reg[0],   DBRIState),
+        VMSTATE_UINT32(reg[1],   DBRIState),
+        VMSTATE_UINT32(pio,      DBRIState),
+        VMSTATE_UINT32(cmdq_ptr, DBRIState),
+        VMSTATE_UINT32(intq_ptr, DBRIState),
+        VMSTATE_UINT32(intq_idx, DBRIState),
+        VMSTATE_UINT32(chi_global_mode, DBRIState),
+        VMSTATE_UINT32(chi_data_mode, DBRIState),
+
+        VMSTATE_UINT32(play.pipe,  DBRIState),
+        VMSTATE_UINT32(rec.pipe,   DBRIState),
+        VMSTATE_UINT32(play.ctrl,  DBRIState),
+        VMSTATE_UINT32(rec.ctrl,   DBRIState),
+        VMSTATE_BOOL(play.stopped, DBRIState),
+        VMSTATE_BOOL(rec.stopped,  DBRIState),
+
+        VMSTATE_BOOL(pipe_update,  DBRIState),
+        VMSTATE_STRUCT_ARRAY(pipe, DBRIState, 32, 1,
+                             vmstate_dbri_pipe, DBRIPipeState),
+
+        VMSTATE_UINT8(codec.status,         DBRIState),
+        VMSTATE_UINT8(codec.data_format,    DBRIState),
+        VMSTATE_UINT8(codec.port_control,   DBRIState),
+        VMSTATE_UINT8_ARRAY(codec.settings, DBRIState, 4),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static SysBusDeviceInfo dbri_info = {
+    .init = dbri_init1,
+    .qdev.name  = "SUNW,DBRIe",
+    .qdev.desc  = "Sun DBRI audio interface",
+    .qdev.size  = sizeof(DBRIState),
+    .qdev.reset = dbri_reset,
+    .qdev.vmsd  = &vmstate_dbri,
+    .qdev.props = (Property[]) {
+        DEFINE_PROP_PTR("iommu_opaque",   DBRIState, iommu),
+        DEFINE_PROP_INT32("codec_offset", DBRIState, codec_offset, 8),
+        DEFINE_PROP_END_OF_LIST(),
+    }
+};
+
+static void dbri_register_devices(void)
+{
+    sysbus_register_withprop(&dbri_info);
+}
+
+device_init(dbri_register_devices);
diff --git a/hw/sun4m.c b/hw/sun4m.c
index df3aa32..3112d19 100644
--- a/hw/sun4m.c
+++ b/hw/sun4m.c
@@ -421,6 +421,20 @@  static void lance_init(NICInfo *nd, target_phys_addr_t leaddr,
     qdev_connect_gpio_out(dma_opaque, 0, reset);
 }

+static void dbri_init(target_phys_addr_t daddr, qemu_irq parent_irq,
+                      void *iommu)
+{
+    DeviceState *dev;
+    SysBusDevice *s;
+
+    dev = qdev_create(NULL, "SUNW,DBRIe");
+    qdev_prop_set_ptr(dev, "iommu_opaque", iommu);
+    qdev_init_nofail(dev);
+    s = sysbus_from_qdev(dev);
+    sysbus_connect_irq(s, 0, parent_irq);
+    sysbus_mmio_map(s, 0, daddr);
+}
+
 static DeviceState *slavio_intctl_init(target_phys_addr_t addr,
                                        target_phys_addr_t addrg,
                                        qemu_irq **parent_irq)
@@ -943,10 +957,7 @@  static void sun4m_hw_init(const struct sun4m_hwdef *hwdef, ram_addr_t RAM_size,

     if (hwdef->dbri_base) {
         /* ISDN chip with attached CS4215 audio codec */
-        /* prom space */
-        empty_slot_init(hwdef->dbri_base+0x1000, 0x30);
-        /* reg space */
-        empty_slot_init(hwdef->dbri_base+0x10000, 0x100);
+        dbri_init(hwdef->dbri_base, slavio_irq[11], iommu);
     }

     if (hwdef->bpp_base) {