Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 1 Mar 2012 13:10:18 +0000 (UTC)
From:      Alexander Motin <mav@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r232337 - in head: share/man/man4 sys/conf sys/dev/sound/pci sys/modules/sound/driver sys/modules/sound/driver/hdspe
Message-ID:  <201203011310.q21DAIs1079768@svn.freebsd.org>

next in thread | raw e-mail | index | archive | help
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 <br@bsdpad.com>
  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 <br@bsdpad.com>
+.\" 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 <br@bsdpad.com> .

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 <br@bsdpad.com>
+ * 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 <br@bsdpad.com>
+ * 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 ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201203011310.q21DAIs1079768>