From owner-svn-src-head@FreeBSD.ORG Thu Mar 1 13:10:18 2012 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id AF0A4106564A; Thu, 1 Mar 2012 13:10:18 +0000 (UTC) (envelope-from mav@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id 96D178FC1C; Thu, 1 Mar 2012 13:10:18 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.4/8.14.4) with ESMTP id q21DAIFP079777; Thu, 1 Mar 2012 13:10:18 GMT (envelope-from mav@svn.freebsd.org) Received: (from mav@localhost) by svn.freebsd.org (8.14.4/8.14.4/Submit) id q21DAIs1079768; Thu, 1 Mar 2012 13:10:18 GMT (envelope-from mav@svn.freebsd.org) Message-Id: <201203011310.q21DAIs1079768@svn.freebsd.org> From: Alexander Motin Date: Thu, 1 Mar 2012 13:10:18 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r232337 - in head: share/man/man4 sys/conf sys/dev/sound/pci sys/modules/sound/driver sys/modules/sound/driver/hdspe X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 01 Mar 2012 13:10:18 -0000 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 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 +.\" 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 . 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 + * 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 +#include +#include + +#include +#include + +#include + +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 + * 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 +#include +#include + +#include +#include + +#include + +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 ***