Author: mav
Date: Thu Mar  1 13:10:18 2012
New Revision: 232337
URL: http://svn.freebsd.org/changeset/base/232337

Log:
  Add driver for the RME HDSPe AIO/RayDAT sound cards -- snd_hdspe(4).
  Cards are expensive and so rare, so leave the driver as module.
  
  Submitted by: Ruslan Bukin <[email protected]>
  MFC after:    2 weeks

Added:
  head/share/man/man4/snd_hdspe.4   (contents, props changed)
  head/sys/dev/sound/pci/hdspe-pcm.c   (contents, props changed)
  head/sys/dev/sound/pci/hdspe.c   (contents, props changed)
  head/sys/dev/sound/pci/hdspe.h   (contents, props changed)
  head/sys/modules/sound/driver/hdspe/
  head/sys/modules/sound/driver/hdspe/Makefile   (contents, props changed)
Modified:
  head/share/man/man4/Makefile
  head/share/man/man4/pcm.4
  head/sys/conf/NOTES
  head/sys/conf/files
  head/sys/modules/sound/driver/Makefile

Modified: head/share/man/man4/Makefile
==============================================================================
--- head/share/man/man4/Makefile        Thu Mar  1 12:52:14 2012        
(r232336)
+++ head/share/man/man4/Makefile        Thu Mar  1 13:10:18 2012        
(r232337)
@@ -414,6 +414,7 @@ MAN=        aac.4 \
        snd_fm801.4 \
        snd_gusc.4 \
        snd_hda.4 \
+       snd_hdspe.4 \
        snd_ich.4 \
        snd_maestro3.4 \
        snd_maestro.4 \

Modified: head/share/man/man4/pcm.4
==============================================================================
--- head/share/man/man4/pcm.4   Thu Mar  1 12:52:14 2012        (r232336)
+++ head/share/man/man4/pcm.4   Thu Mar  1 13:10:18 2012        (r232337)
@@ -113,6 +113,8 @@ The following bridge device drivers are 
 .It
 .Xr snd_hda 4 (enabled by default on amd64, i386)
 .It
+.Xr snd_hdspe 4
+.It
 .Xr snd_ich 4 (enabled by default on amd64, i386)
 .It
 .Xr snd_maestro 4
@@ -723,6 +725,7 @@ A device node is not created properly.
 .Xr snd_fm801 4 ,
 .Xr snd_gusc 4 ,
 .Xr snd_hda 4 ,
+.Xr snd_hdspe 4 ,
 .Xr snd_ich 4 ,
 .Xr snd_maestro 4 ,
 .Xr snd_maestro3 4 ,

Added: head/share/man/man4/snd_hdspe.4
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/share/man/man4/snd_hdspe.4     Thu Mar  1 13:10:18 2012        
(r232337)
@@ -0,0 +1,76 @@
+.\" Copyright (c) 2012 Ruslan Bukin <[email protected]>
+.\" All rights reserved.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd February 13, 2012
+.Dt SND_HDSPE 4
+.Os
+.Sh NAME
+.Nm snd_hdspe
+.Nd "RME HDSPe brigde device driver"
+.Sh SYNOPSIS
+To compile this driver into the kernel, place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device sound"
+.Cd "device snd_hdspe"
+.Ed
+.Pp
+Alternatively, to load the driver as a module at boot time, place the
+following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+snd_hdspe_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+bridge driver allows the generic audio driver
+.Xr sound 4
+to attach to RME HDSPe audio devices.
+.Sh HARDWARE
+The
+.Nm
+driver supports the following audio devices:
+.Pp
+.Bl -bullet -compact
+.It
+RME HDSPe AIO
+.It
+RME HDSPe RayDAT
+.El
+.Sh SEE ALSO
+.Xr sound 4
+.Sh HISTORY
+The
+.Nm
+device driver first appeared in
+.Fx 10.0 .
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Ruslan Bukin <[email protected]> .

Modified: head/sys/conf/NOTES
==============================================================================
--- head/sys/conf/NOTES Thu Mar  1 12:52:14 2012        (r232336)
+++ head/sys/conf/NOTES Thu Mar  1 13:10:18 2012        (r232337)
@@ -2257,6 +2257,7 @@ device            sound
 # snd_gusc:            Gravis UltraSound ISA PnP/non-PnP.
 # snd_hda:             Intel High Definition Audio (Controller) and
 #                      compatible.
+# snd_hdspe:           RME HDSPe AIO and RayDAT.
 # snd_ich:             Intel ICH AC'97 and some more audio controllers
 #                      embedded in a chipset, for example nVidia
 #                      nForce controllers.
@@ -2296,6 +2297,7 @@ device            snd_ess
 device         snd_fm801
 device         snd_gusc
 device         snd_hda
+device         snd_hdspe
 device         snd_ich
 device         snd_maestro
 device         snd_maestro3

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files Thu Mar  1 12:52:14 2012        (r232336)
+++ head/sys/conf/files Thu Mar  1 13:10:18 2012        (r232337)
@@ -1770,6 +1770,8 @@ dev/sound/pci/hda/hdaa_patches.c  optiona
 dev/sound/pci/hda/hdac.c       optional snd_hda pci
 dev/sound/pci/hda/hdac_if.m    optional snd_hda pci
 dev/sound/pci/hda/hdacc.c      optional snd_hda pci
+dev/sound/pci/hdspe.c          optional snd_hdspe pci
+dev/sound/pci/hdspe-pcm.c      optional snd_hdspe pci
 dev/sound/pcm/ac97.c           optional sound
 dev/sound/pcm/ac97_if.m                optional sound
 dev/sound/pcm/ac97_patch.c     optional sound

Added: head/sys/dev/sound/pci/hdspe-pcm.c
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/sys/dev/sound/pci/hdspe-pcm.c  Thu Mar  1 13:10:18 2012        
(r232337)
@@ -0,0 +1,709 @@
+/*-
+ * Copyright (c) 2012 Ruslan Bukin <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * RME HDSPe driver for FreeBSD (pcm-part).
+ * Supported cards: AIO, RayDAT.
+ */
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pci/hdspe.h>
+#include <dev/sound/chip.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <mixer_if.h>
+
+SND_DECLARE_FILE("$FreeBSD$");
+
+struct hdspe_latency {
+       uint32_t n;
+       uint32_t period;
+       float ms;
+};
+
+static struct hdspe_latency latency_map[] = {
+       { 7,   32, 0.7 },
+       { 0,   64, 1.5 },
+       { 1,  128,   3 },
+       { 2,  256,   6 },
+       { 3,  512,  12 },
+       { 4, 1024,  23 },
+       { 5, 2048,  46 },
+       { 6, 4096,  93 },
+
+       { 0,    0,   0 },
+};
+
+struct hdspe_rate {
+       uint32_t speed;
+       uint32_t reg;
+};
+
+static struct hdspe_rate rate_map[] = {
+       {  32000, (HDSPE_FREQ_32000) },
+       {  44100, (HDSPE_FREQ_44100) },
+       {  48000, (HDSPE_FREQ_48000) },
+       {  64000, (HDSPE_FREQ_32000 | HDSPE_FREQ_DOUBLE) },
+       {  88200, (HDSPE_FREQ_44100 | HDSPE_FREQ_DOUBLE) },
+       {  96000, (HDSPE_FREQ_48000 | HDSPE_FREQ_DOUBLE) },
+       { 128000, (HDSPE_FREQ_32000 | HDSPE_FREQ_QUAD)   },
+       { 176400, (HDSPE_FREQ_44100 | HDSPE_FREQ_QUAD)   },
+       { 192000, (HDSPE_FREQ_48000 | HDSPE_FREQ_QUAD)   },
+
+       { 0, 0 },
+};
+
+
+static int
+hdspe_hw_mixer(struct sc_chinfo *ch, unsigned int dst,
+    unsigned int src, unsigned short data)
+{
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+       int offs = 0;
+
+       if (ch->dir == PCMDIR_PLAY)
+               offs = 64;
+
+       hdspe_write_4(sc, HDSPE_MIXER_BASE +
+           ((offs + src + 128 * dst) * sizeof(uint32_t)),
+           data & 0xFFFF);
+
+       return 0;
+};
+
+static int
+hdspechan_setgain(struct sc_chinfo *ch)
+{
+
+       hdspe_hw_mixer(ch, ch->lslot, ch->lslot,
+           ch->lvol * HDSPE_MAX_GAIN / 100);
+       hdspe_hw_mixer(ch, ch->rslot, ch->rslot,
+           ch->rvol * HDSPE_MAX_GAIN / 100);
+
+       return 0;
+}
+
+static int
+hdspemixer_init(struct snd_mixer *m)
+{
+       struct sc_pcminfo *scp = mix_getdevinfo(m);
+       struct sc_info *sc = scp->sc;
+       int mask;
+
+       if (sc == NULL)
+               return -1;
+
+       mask = SOUND_MASK_PCM;
+
+       if (scp->hc->play)
+               mask |= SOUND_MASK_VOLUME;
+
+       if (scp->hc->rec)
+               mask |= SOUND_MASK_RECLEV;
+
+       snd_mtxlock(sc->lock);
+       pcm_setflags(scp->dev, pcm_getflags(scp->dev) | SD_F_SOFTPCMVOL);
+       mix_setdevs(m, mask);
+       snd_mtxunlock(sc->lock);
+
+       return 0;
+}
+
+static int
+hdspemixer_set(struct snd_mixer *m, unsigned dev,
+    unsigned left, unsigned right)
+{
+       struct sc_pcminfo *scp = mix_getdevinfo(m);
+       struct sc_chinfo *ch;
+       int i;
+
+#if 0
+       device_printf(scp->dev, "hdspemixer_set() %d %d\n",
+           left,right);
+#endif
+
+       for (i = 0; i < scp->chnum; i++) {
+               ch = &scp->chan[i];
+               if ((dev == SOUND_MIXER_VOLUME && ch->dir == PCMDIR_PLAY) ||
+                   (dev == SOUND_MIXER_RECLEV && ch->dir == PCMDIR_REC)) {
+                       ch->lvol = left;
+                       ch->rvol = right;
+                       if (ch->run)
+                               hdspechan_setgain(ch);
+               }
+       }
+
+       return 0;
+}
+
+static kobj_method_t hdspemixer_methods[] = {
+       KOBJMETHOD(mixer_init,      hdspemixer_init),
+       KOBJMETHOD(mixer_set,       hdspemixer_set),
+       KOBJMETHOD_END
+};
+MIXER_DECLARE(hdspemixer);
+
+static void
+hdspechan_enable(struct sc_chinfo *ch, int value)
+{
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+       int reg;
+
+       if (ch->dir == PCMDIR_PLAY)
+               reg = HDSPE_OUT_ENABLE_BASE;
+       else
+               reg = HDSPE_IN_ENABLE_BASE;
+
+       ch->run = value;
+
+       hdspe_write_1(sc, reg + (4 * ch->lslot), value);
+       hdspe_write_1(sc, reg + (4 * ch->rslot), value);
+}
+
+static int
+hdspe_running(struct sc_info *sc)
+{
+       struct sc_pcminfo *scp;
+       struct sc_chinfo *ch;
+       int i, j, devcount, err;
+       device_t *devlist;
+
+       if ((err = device_get_children(sc->dev, &devlist, &devcount)) != 0)
+               goto bad;
+
+       for (i = 0; i < devcount; i++) {
+               scp = device_get_ivars(devlist[i]);
+               for (j = 0; j < scp->chnum; j++) {
+                       ch = &scp->chan[j];
+                       if (ch->run)
+                               goto bad;
+               }
+       }
+
+       return 0;
+bad:
+
+#if 0
+       device_printf(sc->dev,"hdspe is running\n");
+#endif
+
+       return 1;
+}
+
+static void
+hdspe_start_audio(struct sc_info *sc)
+{
+
+       sc->ctrl_register |= (HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE);
+       hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+}
+
+static void
+hdspe_stop_audio(struct sc_info *sc)
+{
+
+       if (hdspe_running(sc) == 1)
+               return;
+
+       sc->ctrl_register &= ~(HDSPE_AUDIO_INT_ENABLE | HDSPE_ENABLE);
+       hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+}
+
+/* Multiplex / demultiplex: 2.0 <-> 2 x 1.0. */
+static void
+buffer_copy(struct sc_chinfo *ch)
+{
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+       int length,src,dst;
+       int ssize, dsize;
+       int i;
+
+       length = sndbuf_getready(ch->buffer) /
+           (4 /* Bytes per sample. */ * 2 /* channels */);
+
+       if (ch->dir == PCMDIR_PLAY) {
+               src = sndbuf_getreadyptr(ch->buffer);
+       } else {
+               src = sndbuf_getfreeptr(ch->buffer);
+       }
+
+       src /= 4; /* Bytes per sample. */
+       dst = src / 2; /* Destination buffer twice smaller. */
+
+       ssize = ch->size / 4;
+       dsize = ch->size / 8;
+
+       /*
+        * Use two fragment buffer to avoid sound clipping.
+        */
+
+       for (i = 0; i < sc->period * 2 /* fragments */; i++) {
+               if (ch->dir == PCMDIR_PLAY) {
+                       sc->pbuf[dst + HDSPE_CHANBUF_SAMPLES * ch->lslot] =
+                           ch->data[src];
+                       sc->pbuf[dst + HDSPE_CHANBUF_SAMPLES * ch->rslot] =
+                           ch->data[src + 1];
+
+               } else {
+                       ch->data[src] =
+                           sc->rbuf[dst + HDSPE_CHANBUF_SAMPLES * ch->lslot];
+                       ch->data[src+1] =
+                           sc->rbuf[dst + HDSPE_CHANBUF_SAMPLES * ch->rslot];
+               }
+
+               dst+=1;
+               dst %= dsize;
+               src+=2;
+               src %= ssize;
+       }
+}
+
+static int
+clean(struct sc_chinfo *ch){
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+       uint32_t *buf = sc->rbuf;
+
+       if (ch->dir == PCMDIR_PLAY) {
+               buf = sc->pbuf;
+       }
+
+       bzero(buf + HDSPE_CHANBUF_SAMPLES * ch->lslot, HDSPE_CHANBUF_SIZE);
+       bzero(buf + HDSPE_CHANBUF_SAMPLES * ch->rslot, HDSPE_CHANBUF_SIZE);
+
+       return 0;
+}
+
+
+/* Channel interface. */
+static void *
+hdspechan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b,
+               struct pcm_channel *c, int dir)
+{
+       struct sc_pcminfo *scp = devinfo;
+       struct sc_info *sc = scp->sc;
+       struct sc_chinfo *ch;
+       int num;
+
+       snd_mtxlock(sc->lock);
+       num = scp->chnum;
+
+       ch = &scp->chan[num];
+       ch->lslot = scp->hc->left;
+       ch->rslot = scp->hc->right;
+       ch->run = 0;
+       ch->lvol = 0;
+       ch->rvol = 0;
+
+       ch->size = HDSPE_CHANBUF_SIZE * 2 /* slots */;
+       ch->data = malloc(ch->size, M_HDSPE, M_NOWAIT);
+
+       ch->buffer = b;
+       ch->channel = c;
+       ch->parent = scp;
+
+       ch->dir = dir;
+
+       snd_mtxunlock(sc->lock);
+
+       if (sndbuf_setup(ch->buffer, ch->data, ch->size) != 0) {
+               device_printf(scp->dev, "Can't setup sndbuf.\n");
+               return NULL;
+       }
+
+       return ch;
+}
+
+static int
+hdspechan_trigger(kobj_t obj, void *data, int go)
+{
+       struct sc_chinfo *ch = data;
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+
+       snd_mtxlock(sc->lock);
+       switch (go) {
+       case PCMTRIG_START:
+#if 0
+               device_printf(scp->dev, "hdspechan_trigger(): start\n");
+#endif
+               hdspechan_enable(ch, 1);
+               hdspechan_setgain(ch);
+               hdspe_start_audio(sc);
+               break;
+
+       case PCMTRIG_STOP:
+       case PCMTRIG_ABORT:
+#if 0
+               device_printf(scp->dev, "hdspechan_trigger(): stop or abort\n");
+#endif
+               clean(ch);
+               hdspechan_enable(ch, 0);
+               hdspe_stop_audio(sc);
+               break;
+
+       case PCMTRIG_EMLDMAWR:
+       case PCMTRIG_EMLDMARD:
+               if(ch->run)
+                       buffer_copy(ch);
+               break;
+       }
+
+       snd_mtxunlock(sc->lock);
+
+       return 0;
+}
+
+static uint32_t
+hdspechan_getptr(kobj_t obj, void *data)
+{
+       struct sc_chinfo *ch = data;
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+       uint32_t ret, pos;
+
+       snd_mtxlock(sc->lock);
+       ret = hdspe_read_2(sc, HDSPE_STATUS_REG);
+       snd_mtxunlock(sc->lock);
+
+       pos = ret & HDSPE_BUF_POSITION_MASK;
+       pos *= 2; /* Hardbuf twice bigger. */
+
+       return pos;
+}
+
+static int
+hdspechan_free(kobj_t obj, void *data)
+{
+       struct sc_chinfo *ch = data;
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+
+#if 0
+       device_printf(scp->dev, "hdspechan_free()\n");
+#endif
+       snd_mtxlock(sc->lock);
+       if (ch->data != NULL) {
+               free(ch->data, M_HDSPE);
+               ch->data = NULL;
+       }
+       snd_mtxunlock(sc->lock);
+
+       return 0;
+}
+
+static int
+hdspechan_setformat(kobj_t obj, void *data, uint32_t format)
+{
+       struct sc_chinfo *ch = data;
+
+#if 0
+       struct sc_pcminfo *scp = ch->parent;
+       device_printf(scp->dev, "hdspechan_setformat(%d)\n", format);
+#endif
+
+       ch->format = format;
+
+       return 0;
+}
+
+static uint32_t
+hdspechan_setspeed(kobj_t obj, void *data, uint32_t speed)
+{
+       struct sc_chinfo *ch = data;
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+       struct hdspe_rate *hr = NULL;
+       long long period;
+       int threshold;
+       int i;
+
+#if 0
+       device_printf(scp->dev, "hdspechan_setspeed(%d)\n", speed);
+#endif
+
+       if (hdspe_running(sc) == 1)
+               goto end;
+
+       /* First look for equal frequency. */
+       for (i = 0; rate_map[i].speed != 0; i++) {
+               if (rate_map[i].speed == speed)
+                       hr = &rate_map[i];
+       }
+
+       /* If no match, just find nearest. */
+       if (hr == NULL) {
+               for (i = 0; rate_map[i].speed != 0; i++) {
+                       hr = &rate_map[i];
+                       threshold = hr->speed + ((rate_map[i + 1].speed != 0) ?
+                           ((rate_map[i + 1].speed - hr->speed) >> 1) : 0);
+                       if (speed < threshold)
+                               break;
+               }
+       }
+
+       switch (sc->type) {
+       case RAYDAT:
+       case AIO:
+               period = HDSPE_FREQ_AIO;
+               break;
+       default:
+               /* Unsupported card. */
+               goto end;
+       }
+
+       /* Write frequency on the device. */
+       sc->ctrl_register &= ~HDSPE_FREQ_MASK;
+       sc->ctrl_register |= hr->reg;
+       hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+
+       speed = hr->speed;
+       if (speed > 96000)
+               speed /= 4;
+       else if (speed > 48000)
+               speed /= 2;
+
+       /* Set DDS value. */
+       period /= speed;
+       hdspe_write_4(sc, HDSPE_FREQ_REG, period);
+
+       sc->speed = hr->speed;
+end:
+       return sc->speed;
+}
+
+static uint32_t
+hdspechan_setblocksize(kobj_t obj, void *data, uint32_t blocksize)
+{
+       struct sc_chinfo *ch = data;
+       struct sc_pcminfo *scp = ch->parent;
+       struct sc_info *sc = scp->sc;
+       struct hdspe_latency *hl = NULL;
+       int threshold;
+       int i;
+
+#if 0
+       device_printf(scp->dev, "hdspechan_setblocksize(%d)\n", blocksize);
+#endif
+
+       if (hdspe_running(sc) == 1)
+               goto end;
+
+       if (blocksize > HDSPE_LAT_BYTES_MAX)
+               blocksize = HDSPE_LAT_BYTES_MAX;
+       else if (blocksize < HDSPE_LAT_BYTES_MIN)
+               blocksize = HDSPE_LAT_BYTES_MIN;
+
+       blocksize /= 4 /* samples */;
+
+       /* First look for equal latency. */
+       for (i = 0; latency_map[i].period != 0; i++) {
+               if (latency_map[i].period == blocksize) {
+                       hl = &latency_map[i];
+               }
+       }
+
+       /* If no match, just find nearest. */
+       if (hl == NULL) {
+               for (i = 0; latency_map[i].period != 0; i++) {
+                       hl = &latency_map[i];
+                       threshold = hl->period + ((latency_map[i + 1].period != 
0) ?
+                           ((latency_map[i + 1].period - hl->period) >> 1) : 
0);
+                       if (blocksize < threshold)
+                               break;
+               }
+       }
+
+       snd_mtxlock(sc->lock);
+       sc->ctrl_register &= ~HDSPE_LAT_MASK;
+       sc->ctrl_register |= hdspe_encode_latency(hl->n);
+       hdspe_write_4(sc, HDSPE_CONTROL_REG, sc->ctrl_register);
+       sc->period = hl->period;
+       snd_mtxunlock(sc->lock);
+
+#if 0
+       device_printf(scp->dev, "New period=%d\n", sc->period);
+#endif
+
+       sndbuf_resize(ch->buffer, (HDSPE_CHANBUF_SIZE * 2) / (sc->period * 4),
+           (sc->period * 4));
+end:
+       return sndbuf_getblksz(ch->buffer);
+}
+
+static uint32_t hdspe_rfmt[] = {
+       SND_FORMAT(AFMT_S32_LE, 2, 0),
+       0
+};
+
+static struct pcmchan_caps hdspe_rcaps = {32000, 192000, hdspe_rfmt, 0};
+
+static uint32_t hdspe_pfmt[] = {
+       SND_FORMAT(AFMT_S32_LE, 2, 0),
+       0
+};
+
+static struct pcmchan_caps hdspe_pcaps = {32000, 192000, hdspe_pfmt, 0};
+
+static struct pcmchan_caps *
+hdspechan_getcaps(kobj_t obj, void *data)
+{
+       struct sc_chinfo *ch = data;
+
+#if 0
+       struct sc_pcminfo *scl = ch->parent;
+       device_printf(scp->dev, "hdspechan_getcaps()\n");
+#endif
+
+       return (ch->dir == PCMDIR_PLAY) ?
+           &hdspe_pcaps : &hdspe_rcaps;
+}
+
+static kobj_method_t hdspechan_methods[] = {
+       KOBJMETHOD(channel_init,         hdspechan_init),
+       KOBJMETHOD(channel_free,         hdspechan_free),
+       KOBJMETHOD(channel_setformat,    hdspechan_setformat),
+       KOBJMETHOD(channel_setspeed,     hdspechan_setspeed),
+       KOBJMETHOD(channel_setblocksize, hdspechan_setblocksize),
+       KOBJMETHOD(channel_trigger,      hdspechan_trigger),
+       KOBJMETHOD(channel_getptr,       hdspechan_getptr),
+       KOBJMETHOD(channel_getcaps,      hdspechan_getcaps),
+       KOBJMETHOD_END
+};
+CHANNEL_DECLARE(hdspechan);
+
+
+static int
+hdspe_pcm_probe(device_t dev)
+{
+
+#if 0
+       device_printf(dev,"hdspe_pcm_probe()\n");
+#endif
+
+       return 0;
+}
+
+static uint32_t
+hdspe_pcm_intr(struct sc_pcminfo *scp) {
+       struct sc_chinfo *ch;
+       struct sc_info *sc = scp->sc;
+       int i;
+
+       for (i = 0; i < scp->chnum; i++) {
+               ch = &scp->chan[i];
+               snd_mtxunlock(sc->lock);
+               chn_intr(ch->channel);
+               snd_mtxlock(sc->lock);
+       }
+
+       return 0;
+}
+
+static int
+hdspe_pcm_attach(device_t dev)
+{
+       struct sc_pcminfo *scp;
+       char status[SND_STATUSLEN];
+       char desc[64];
+       int i, err;
+
+       scp = device_get_ivars(dev);
+       scp->ih = &hdspe_pcm_intr;
+
+       bzero(desc, sizeof(desc));
+       snprintf(desc, sizeof(desc), "HDSPe AIO [%s]", scp->hc->descr);
+       device_set_desc_copy(dev, desc);
+
+       /*
+        * We don't register interrupt handler with snd_setup_intr
+        * in pcm device. Mark pcm device as MPSAFE manually.
+        */
+       pcm_setflags(dev, pcm_getflags(dev) | SD_F_MPSAFE);
+
+       err = pcm_register(dev, scp, scp->hc->play, scp->hc->rec);
+       if (err) {
+               device_printf(dev, "Can't register pcm.\n");
+               return ENXIO;
+       }
+
+       scp->chnum = 0;
+       for (i = 0; i < scp->hc->play; i++) {
+               pcm_addchan(dev, PCMDIR_PLAY, &hdspechan_class, scp);
+               scp->chnum++;
+       }
+
+       for (i = 0; i < scp->hc->rec; i++) {
+               pcm_addchan(dev, PCMDIR_REC, &hdspechan_class, scp);
+               scp->chnum++;
+       }
+
+       snprintf(status, SND_STATUSLEN, "at io 0x%lx irq %ld %s",
+           rman_get_start(scp->sc->cs),
+           rman_get_start(scp->sc->irq),
+           PCM_KLDSTRING(snd_hdspe));
+       pcm_setstatus(dev, status);
+
+       mixer_init(dev, &hdspemixer_class, scp);
+
+       return 0;
+}
+
+static int
+hdspe_pcm_detach(device_t dev)
+{
+       int err;
+
+       err = pcm_unregister(dev);
+       if (err) {
+               device_printf(dev, "Can't unregister device.\n");
+               return err;
+       }
+
+       return 0;
+}
+
+static device_method_t hdspe_pcm_methods[] = {
+       DEVMETHOD(device_probe,     hdspe_pcm_probe),
+       DEVMETHOD(device_attach,    hdspe_pcm_attach),
+       DEVMETHOD(device_detach,    hdspe_pcm_detach),
+       { 0, 0 }
+};
+
+static driver_t hdspe_pcm_driver = {
+       "pcm",
+       hdspe_pcm_methods,
+       PCM_SOFTC_SIZE,
+};
+
+DRIVER_MODULE(snd_hdspe_pcm, hdspe, hdspe_pcm_driver, pcm_devclass, 0, 0);
+MODULE_DEPEND(snd_hdspe, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER);
+MODULE_VERSION(snd_hdspe, 1);

Added: head/sys/dev/sound/pci/hdspe.c
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/sys/dev/sound/pci/hdspe.c      Thu Mar  1 13:10:18 2012        
(r232337)
@@ -0,0 +1,410 @@
+/*-
+ * Copyright (c) 2012 Ruslan Bukin <[email protected]>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * RME HDSPe driver for FreeBSD.
+ * Supported cards: AIO, RayDAT.
+ */
+
+#include <dev/sound/pcm/sound.h>
+#include <dev/sound/pci/hdspe.h>
+#include <dev/sound/chip.h>
+
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+
+#include <mixer_if.h>
+
+SND_DECLARE_FILE("$FreeBSD$");
+
+static struct hdspe_channel chan_map_aio[] = {
+       {  0,  1,   "line", 1, 1 },
+       {  6,  7,  "phone", 1, 0 },
+       {  8,  9,    "aes", 1, 1 },
+       { 10, 11, "s/pdif", 1, 1 },
+       { 12, 16,   "adat", 1, 1 },
+
+       /* Single or double speed. */
+       { 14, 18,   "adat", 1, 1 },
+
+       /* Single speed only. */
+       { 13, 15,   "adat", 1, 1 },
+       { 17, 19,   "adat", 1, 1 },
+
+       {  0,  0,     NULL, 0, 0 },
+};
+
+static struct hdspe_channel chan_map_rd[] = {
+       {   0, 1,    "aes", 1, 1 },
+       {   2, 3, "s/pdif", 1, 1 },
+       {   4, 5,   "adat", 1, 1 },
+       {   6, 7,   "adat", 1, 1 },
+       {   8, 9,   "adat", 1, 1 },
+       { 10, 11,   "adat", 1, 1 },
+
+       /* Single or double speed. */
+       { 12, 13,   "adat", 1, 1 },
+       { 14, 15,   "adat", 1, 1 },
+       { 16, 17,   "adat", 1, 1 },
+       { 18, 19,   "adat", 1, 1 },
+
+       /* Single speed only. */
+       { 20, 21,   "adat", 1, 1 },
+       { 22, 23,   "adat", 1, 1 },
+       { 24, 25,   "adat", 1, 1 },
+       { 26, 27,   "adat", 1, 1 },
+       { 28, 29,   "adat", 1, 1 },
+       { 30, 31,   "adat", 1, 1 },
+       { 32, 33,   "adat", 1, 1 },
+       { 34, 35,   "adat", 1, 1 },
+
+       { 0,  0,      NULL, 0, 0 },
+};
+
+static void
+hdspe_intr(void *p)
+{
+       struct sc_info *sc = (struct sc_info *)p;
+       struct sc_pcminfo *scp;
+       device_t *devlist;
+       int devcount, status;
+       int i, err;
+
+       snd_mtxlock(sc->lock);
+
+       status = hdspe_read_1(sc, HDSPE_STATUS_REG);
+       if (status & HDSPE_AUDIO_IRQ_PENDING) {
+               if ((err = device_get_children(sc->dev, &devlist, &devcount)) 
!= 0)
+                       return;
+
+               for (i = 0; i < devcount; i++) {
+                       scp = device_get_ivars(devlist[i]);
+                       if (scp->ih != NULL)
+                               scp->ih(scp);
+               }
+
+               hdspe_write_1(sc, HDSPE_INTERRUPT_ACK, 0);
+       }
+
+       snd_mtxunlock(sc->lock);
+}
+
+static void
+hdspe_dmapsetmap(void *arg, bus_dma_segment_t *segs, int nseg, int error)
+{
+#if 0
+       struct sc_info *sc = (struct sc_info *)arg;
+       device_printf(sc->dev, "hdspe_dmapsetmap()\n");
+#endif
+}
+
+static int
+hdspe_alloc_resources(struct sc_info *sc)
+{
+
+       /* Allocate resource. */
+       sc->csid = PCIR_BAR(0);
+       sc->cs = bus_alloc_resource(sc->dev, SYS_RES_MEMORY,
+           &sc->csid, 0, ~0, 1, RF_ACTIVE);
+
+       if (!sc->cs) {
+               device_printf(sc->dev, "Unable to map SYS_RES_MEMORY.\n");
+               return (ENXIO);
+       }
+       sc->cst = rman_get_bustag(sc->cs);
+       sc->csh = rman_get_bushandle(sc->cs);
+
+
+       /* Allocate interrupt resource. */
+       sc->irqid = 0;
+       sc->irq = bus_alloc_resource(sc->dev, SYS_RES_IRQ, &sc->irqid,
+           0, ~0, 1, RF_ACTIVE | RF_SHAREABLE);
+
+       if (!sc->irq ||
+           bus_setup_intr(sc->dev, sc->irq, INTR_MPSAFE | INTR_TYPE_AV,
+               NULL, hdspe_intr, sc, &sc->ih)) {

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
_______________________________________________
[email protected] mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "[email protected]"

Reply via email to