xref: /freebsd/sys/dev/sound/pcm/vchan.c (revision 97d69a96207d6698f9f88d57dd4be5000688c548)
1098ca2bdSWarner Losh /*-
23f225978SCameron Grant  * Copyright (c) 2001 Cameron Grant <cg@freebsd.org>
3285648f9SCameron Grant  * All rights reserved.
4285648f9SCameron Grant  *
5285648f9SCameron Grant  * Redistribution and use in source and binary forms, with or without
6285648f9SCameron Grant  * modification, are permitted provided that the following conditions
7285648f9SCameron Grant  * are met:
8285648f9SCameron Grant  * 1. Redistributions of source code must retain the above copyright
9285648f9SCameron Grant  *    notice, this list of conditions and the following disclaimer.
10285648f9SCameron Grant  * 2. Redistributions in binary form must reproduce the above copyright
11285648f9SCameron Grant  *    notice, this list of conditions and the following disclaimer in the
12285648f9SCameron Grant  *    documentation and/or other materials provided with the distribution.
13285648f9SCameron Grant  *
14285648f9SCameron Grant  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15285648f9SCameron Grant  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16285648f9SCameron Grant  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17285648f9SCameron Grant  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18285648f9SCameron Grant  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19285648f9SCameron Grant  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20285648f9SCameron Grant  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21285648f9SCameron Grant  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22285648f9SCameron Grant  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23285648f9SCameron Grant  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24285648f9SCameron Grant  * SUCH DAMAGE.
25285648f9SCameron Grant  */
26285648f9SCameron Grant 
27285648f9SCameron Grant #include <dev/sound/pcm/sound.h>
28285648f9SCameron Grant #include <dev/sound/pcm/vchan.h>
29285648f9SCameron Grant #include "feeder_if.h"
30285648f9SCameron Grant 
3167b1dce3SCameron Grant SND_DECLARE_FILE("$FreeBSD$");
3267b1dce3SCameron Grant 
3387506547SAlexander Leidinger /*
3487506547SAlexander Leidinger  * Default speed
3587506547SAlexander Leidinger  */
3687506547SAlexander Leidinger #define VCHAN_DEFAULT_SPEED	48000
3787506547SAlexander Leidinger 
3887506547SAlexander Leidinger extern int feeder_rate_ratemin;
3987506547SAlexander Leidinger extern int feeder_rate_ratemax;
4087506547SAlexander Leidinger 
41285648f9SCameron Grant struct vchinfo {
4249c5e6e2SCameron Grant 	u_int32_t spd, fmt, blksz, bps, run;
43285648f9SCameron Grant 	struct pcm_channel *channel, *parent;
44285648f9SCameron Grant 	struct pcmchan_caps caps;
45285648f9SCameron Grant };
46285648f9SCameron Grant 
47285648f9SCameron Grant static u_int32_t vchan_fmt[] = {
48285648f9SCameron Grant 	AFMT_STEREO | AFMT_S16_LE,
49285648f9SCameron Grant 	0
50285648f9SCameron Grant };
51285648f9SCameron Grant 
52285648f9SCameron Grant static int
53285648f9SCameron Grant vchan_mix_s16(int16_t *to, int16_t *tmp, unsigned int count)
54285648f9SCameron Grant {
55285648f9SCameron Grant 	/*
56285648f9SCameron Grant 	 * to is the output buffer, tmp is the input buffer
57285648f9SCameron Grant 	 * count is the number of 16bit samples to mix
58285648f9SCameron Grant 	 */
59285648f9SCameron Grant 	int i;
60285648f9SCameron Grant 	int x;
61285648f9SCameron Grant 
62285648f9SCameron Grant 	for(i = 0; i < count; i++) {
63285648f9SCameron Grant 		x = to[i];
64285648f9SCameron Grant 		x += tmp[i];
65285648f9SCameron Grant 		if (x < -32768) {
66285648f9SCameron Grant 			/* printf("%d + %d = %d (u)\n", to[i], tmp[i], x); */
67285648f9SCameron Grant 			x = -32768;
68285648f9SCameron Grant 		}
69285648f9SCameron Grant 		if (x > 32767) {
70285648f9SCameron Grant 			/* printf("%d + %d = %d (o)\n", to[i], tmp[i], x); */
71285648f9SCameron Grant 			x = 32767;
72285648f9SCameron Grant 		}
73285648f9SCameron Grant 		to[i] = x & 0x0000ffff;
74285648f9SCameron Grant 	}
75285648f9SCameron Grant 	return 0;
76285648f9SCameron Grant }
77285648f9SCameron Grant 
78285648f9SCameron Grant static int
79285648f9SCameron Grant feed_vchan_s16(struct pcm_feeder *f, struct pcm_channel *c, u_int8_t *b, u_int32_t count, void *source)
80285648f9SCameron Grant {
81285648f9SCameron Grant 	/* we're going to abuse things a bit */
82285648f9SCameron Grant 	struct snd_dbuf *src = source;
83285648f9SCameron Grant 	struct pcmchan_children *cce;
84285648f9SCameron Grant 	struct pcm_channel *ch;
8597d69a96SAlexander Leidinger 	uint32_t sz;
86285648f9SCameron Grant 	int16_t *tmp, *dst;
8797d69a96SAlexander Leidinger 	unsigned int cnt, rcnt = 0;
88285648f9SCameron Grant 
8997d69a96SAlexander Leidinger 	#if 0
9012e524a2SDon Lewis 	if (sndbuf_getsize(src) < count)
9112e524a2SDon Lewis 		panic("feed_vchan_s16(%s): tmp buffer size %d < count %d, flags = 0x%x",
9212e524a2SDon Lewis 		    c->name, sndbuf_getsize(src), count, c->flags);
9397d69a96SAlexander Leidinger 	#endif
9497d69a96SAlexander Leidinger 	sz = sndbuf_getsize(src);
9597d69a96SAlexander Leidinger 	if (sz < count)
9697d69a96SAlexander Leidinger 		count = sz;
97285648f9SCameron Grant 	count &= ~1;
9897d69a96SAlexander Leidinger 	if (count < 2)
9997d69a96SAlexander Leidinger 		return 0;
100285648f9SCameron Grant 	bzero(b, count);
101285648f9SCameron Grant 
102285648f9SCameron Grant 	/*
103285648f9SCameron Grant 	 * we are going to use our source as a temporary buffer since it's
104285648f9SCameron Grant 	 * got no other purpose.  we obtain our data by traversing the channel
105285648f9SCameron Grant 	 * list of children and calling vchan_mix_* to mix count bytes from each
106285648f9SCameron Grant 	 * into our destination buffer, b
107285648f9SCameron Grant 	 */
108285648f9SCameron Grant 	dst = (int16_t *)b;
109285648f9SCameron Grant 	tmp = (int16_t *)sndbuf_getbuf(src);
110285648f9SCameron Grant 	bzero(tmp, count);
111285648f9SCameron Grant 	SLIST_FOREACH(cce, &c->children, link) {
112285648f9SCameron Grant 		ch = cce->channel;
11312e524a2SDon Lewis    		CHN_LOCK(ch);
11449c5e6e2SCameron Grant 		if (ch->flags & CHN_F_TRIGGERED) {
115193d5719SCameron Grant 			if (ch->flags & CHN_F_MAPPED)
116193d5719SCameron Grant 				sndbuf_acquire(ch->bufsoft, NULL, sndbuf_getfree(ch->bufsoft));
117285648f9SCameron Grant 			cnt = FEEDER_FEED(ch->feeder, ch, (u_int8_t *)tmp, count, ch->bufsoft);
11897d69a96SAlexander Leidinger 			vchan_mix_s16(dst, tmp, cnt >> 1);
11997d69a96SAlexander Leidinger 			if (cnt > rcnt)
12097d69a96SAlexander Leidinger 				rcnt = cnt;
121285648f9SCameron Grant 		}
12212e524a2SDon Lewis    		CHN_UNLOCK(ch);
12349c5e6e2SCameron Grant 	}
124285648f9SCameron Grant 
12597d69a96SAlexander Leidinger 	return rcnt & ~1;
126285648f9SCameron Grant }
127285648f9SCameron Grant 
128285648f9SCameron Grant static struct pcm_feederdesc feeder_vchan_s16_desc[] = {
129285648f9SCameron Grant 	{FEEDER_MIXER, AFMT_S16_LE | AFMT_STEREO, AFMT_S16_LE | AFMT_STEREO, 0},
130285648f9SCameron Grant 	{0},
131285648f9SCameron Grant };
132285648f9SCameron Grant static kobj_method_t feeder_vchan_s16_methods[] = {
133285648f9SCameron Grant     	KOBJMETHOD(feeder_feed,		feed_vchan_s16),
134285648f9SCameron Grant 	{ 0, 0 }
135285648f9SCameron Grant };
136285648f9SCameron Grant FEEDER_DECLARE(feeder_vchan_s16, 2, NULL);
137285648f9SCameron Grant 
138285648f9SCameron Grant /************************************************************/
139285648f9SCameron Grant 
140285648f9SCameron Grant static void *
141285648f9SCameron Grant vchan_init(kobj_t obj, void *devinfo, struct snd_dbuf *b, struct pcm_channel *c, int dir)
142285648f9SCameron Grant {
143285648f9SCameron Grant 	struct vchinfo *ch;
144285648f9SCameron Grant 	struct pcm_channel *parent = devinfo;
145285648f9SCameron Grant 
146285648f9SCameron Grant 	KASSERT(dir == PCMDIR_PLAY, ("vchan_init: bad direction"));
147a163d034SWarner Losh 	ch = malloc(sizeof(*ch), M_DEVBUF, M_WAITOK | M_ZERO);
14897d69a96SAlexander Leidinger 	if (!ch)
14997d69a96SAlexander Leidinger 		return NULL;
150285648f9SCameron Grant 	ch->parent = parent;
151285648f9SCameron Grant 	ch->channel = c;
152285648f9SCameron Grant 	ch->fmt = AFMT_U8;
153285648f9SCameron Grant 	ch->spd = DSP_DEFAULT_SPEED;
154285648f9SCameron Grant 	ch->blksz = 2048;
155285648f9SCameron Grant 
156285648f9SCameron Grant 	c->flags |= CHN_F_VIRTUAL;
157285648f9SCameron Grant 
158285648f9SCameron Grant 	return ch;
159285648f9SCameron Grant }
160285648f9SCameron Grant 
161285648f9SCameron Grant static int
162285648f9SCameron Grant vchan_free(kobj_t obj, void *data)
163285648f9SCameron Grant {
164285648f9SCameron Grant 	return 0;
165285648f9SCameron Grant }
166285648f9SCameron Grant 
167285648f9SCameron Grant static int
168285648f9SCameron Grant vchan_setformat(kobj_t obj, void *data, u_int32_t format)
169285648f9SCameron Grant {
170285648f9SCameron Grant 	struct vchinfo *ch = data;
171285648f9SCameron Grant 	struct pcm_channel *parent = ch->parent;
17212e524a2SDon Lewis 	struct pcm_channel *channel = ch->channel;
173285648f9SCameron Grant 
174285648f9SCameron Grant 	ch->fmt = format;
17549c5e6e2SCameron Grant 	ch->bps = 1;
17649c5e6e2SCameron Grant 	ch->bps <<= (ch->fmt & AFMT_STEREO)? 1 : 0;
17787506547SAlexander Leidinger 	if (ch->fmt & AFMT_16BIT)
17887506547SAlexander Leidinger 		ch->bps <<= 1;
17987506547SAlexander Leidinger 	else if (ch->fmt & AFMT_24BIT)
18087506547SAlexander Leidinger 		ch->bps *= 3;
18187506547SAlexander Leidinger 	else if (ch->fmt & AFMT_32BIT)
18287506547SAlexander Leidinger 		ch->bps <<= 2;
18312e524a2SDon Lewis    	CHN_UNLOCK(channel);
184285648f9SCameron Grant 	chn_notify(parent, CHN_N_FORMAT);
18512e524a2SDon Lewis    	CHN_LOCK(channel);
18697d69a96SAlexander Leidinger 	sndbuf_setfmt(channel->bufsoft, format);
187285648f9SCameron Grant 	return 0;
188285648f9SCameron Grant }
189285648f9SCameron Grant 
190285648f9SCameron Grant static int
191285648f9SCameron Grant vchan_setspeed(kobj_t obj, void *data, u_int32_t speed)
192285648f9SCameron Grant {
193285648f9SCameron Grant 	struct vchinfo *ch = data;
194285648f9SCameron Grant 	struct pcm_channel *parent = ch->parent;
19512e524a2SDon Lewis 	struct pcm_channel *channel = ch->channel;
196285648f9SCameron Grant 
197285648f9SCameron Grant 	ch->spd = speed;
19812e524a2SDon Lewis    	CHN_UNLOCK(channel);
199285648f9SCameron Grant 	chn_notify(parent, CHN_N_RATE);
20012e524a2SDon Lewis    	CHN_LOCK(channel);
201285648f9SCameron Grant 	return speed;
202285648f9SCameron Grant }
203285648f9SCameron Grant 
204285648f9SCameron Grant static int
205285648f9SCameron Grant vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
206285648f9SCameron Grant {
207285648f9SCameron Grant 	struct vchinfo *ch = data;
20897d69a96SAlexander Leidinger 	struct pcm_channel *channel = ch->channel;
209285648f9SCameron Grant 	struct pcm_channel *parent = ch->parent;
21012e524a2SDon Lewis 	/* struct pcm_channel *channel = ch->channel; */
21149c5e6e2SCameron Grant 	int prate, crate;
212285648f9SCameron Grant 
213285648f9SCameron Grant 	ch->blksz = blocksize;
21412e524a2SDon Lewis    	/* CHN_UNLOCK(channel); */
21597d69a96SAlexander Leidinger 	sndbuf_setblksz(channel->bufhard, blocksize);
216285648f9SCameron Grant 	chn_notify(parent, CHN_N_BLOCKSIZE);
21712e524a2SDon Lewis    	CHN_LOCK(parent);
21812e524a2SDon Lewis    	/* CHN_LOCK(channel); */
21949c5e6e2SCameron Grant 
22049c5e6e2SCameron Grant 	crate = ch->spd * ch->bps;
22149c5e6e2SCameron Grant 	prate = sndbuf_getspd(parent->bufhard) * sndbuf_getbps(parent->bufhard);
22249c5e6e2SCameron Grant 	blocksize = sndbuf_getblksz(parent->bufhard);
22312e524a2SDon Lewis    	CHN_UNLOCK(parent);
22449c5e6e2SCameron Grant 	blocksize *= prate;
22549c5e6e2SCameron Grant 	blocksize /= crate;
22649c5e6e2SCameron Grant 
227285648f9SCameron Grant 	return blocksize;
228285648f9SCameron Grant }
229285648f9SCameron Grant 
230285648f9SCameron Grant static int
231285648f9SCameron Grant vchan_trigger(kobj_t obj, void *data, int go)
232285648f9SCameron Grant {
233285648f9SCameron Grant 	struct vchinfo *ch = data;
234285648f9SCameron Grant 	struct pcm_channel *parent = ch->parent;
23512e524a2SDon Lewis 	struct pcm_channel *channel = ch->channel;
236285648f9SCameron Grant 
237285648f9SCameron Grant 	if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
238285648f9SCameron Grant 		return 0;
239285648f9SCameron Grant 
240285648f9SCameron Grant 	ch->run = (go == PCMTRIG_START)? 1 : 0;
24112e524a2SDon Lewis    	CHN_UNLOCK(channel);
242285648f9SCameron Grant 	chn_notify(parent, CHN_N_TRIGGER);
24312e524a2SDon Lewis    	CHN_LOCK(channel);
244285648f9SCameron Grant 
245285648f9SCameron Grant 	return 0;
246285648f9SCameron Grant }
247285648f9SCameron Grant 
248285648f9SCameron Grant static struct pcmchan_caps *
249285648f9SCameron Grant vchan_getcaps(kobj_t obj, void *data)
250285648f9SCameron Grant {
251285648f9SCameron Grant 	struct vchinfo *ch = data;
252285648f9SCameron Grant 
253285648f9SCameron Grant 	ch->caps.minspeed = sndbuf_getspd(ch->parent->bufhard);
254285648f9SCameron Grant 	ch->caps.maxspeed = ch->caps.minspeed;
255285648f9SCameron Grant 	ch->caps.fmtlist = vchan_fmt;
256285648f9SCameron Grant 	ch->caps.caps = 0;
257285648f9SCameron Grant 
258285648f9SCameron Grant 	return &ch->caps;
259285648f9SCameron Grant }
260285648f9SCameron Grant 
261285648f9SCameron Grant static kobj_method_t vchan_methods[] = {
262285648f9SCameron Grant     	KOBJMETHOD(channel_init,		vchan_init),
263285648f9SCameron Grant     	KOBJMETHOD(channel_free,		vchan_free),
264285648f9SCameron Grant     	KOBJMETHOD(channel_setformat,		vchan_setformat),
265285648f9SCameron Grant     	KOBJMETHOD(channel_setspeed,		vchan_setspeed),
266285648f9SCameron Grant     	KOBJMETHOD(channel_setblocksize,	vchan_setblocksize),
267285648f9SCameron Grant     	KOBJMETHOD(channel_trigger,		vchan_trigger),
268285648f9SCameron Grant     	KOBJMETHOD(channel_getcaps,		vchan_getcaps),
269285648f9SCameron Grant 	{ 0, 0 }
270285648f9SCameron Grant };
271285648f9SCameron Grant CHANNEL_DECLARE(vchan);
272285648f9SCameron Grant 
27387506547SAlexander Leidinger /*
27487506547SAlexander Leidinger  * On the fly vchan rate settings
27587506547SAlexander Leidinger  */
27687506547SAlexander Leidinger #ifdef SND_DYNSYSCTL
27787506547SAlexander Leidinger static int
27887506547SAlexander Leidinger sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS)
27987506547SAlexander Leidinger {
28087506547SAlexander Leidinger 	struct snddev_info *d;
28187506547SAlexander Leidinger     	struct snddev_channel *sce;
28297d69a96SAlexander Leidinger 	struct pcm_channel *c, *ch = NULL, *fake;
28387506547SAlexander Leidinger 	struct pcmchan_caps *caps;
28487506547SAlexander Leidinger 	int err = 0;
28587506547SAlexander Leidinger 	int newspd = 0;
28687506547SAlexander Leidinger 
28787506547SAlexander Leidinger 	d = oidp->oid_arg1;
28897d69a96SAlexander Leidinger 	if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1)
28987506547SAlexander Leidinger 		return EINVAL;
29087506547SAlexander Leidinger 	SLIST_FOREACH(sce, &d->channels, link) {
29187506547SAlexander Leidinger 		c = sce->channel;
29287506547SAlexander Leidinger 		CHN_LOCK(c);
29387506547SAlexander Leidinger 		if (c->direction == PCMDIR_PLAY) {
29487506547SAlexander Leidinger 			if (c->flags & CHN_F_VIRTUAL) {
29587506547SAlexander Leidinger 				if (req->newptr != NULL &&
29687506547SAlexander Leidinger 						(c->flags & CHN_F_BUSY)) {
29787506547SAlexander Leidinger 					CHN_UNLOCK(c);
29887506547SAlexander Leidinger 					return EBUSY;
29987506547SAlexander Leidinger 				}
30097d69a96SAlexander Leidinger 				if (ch == NULL)
30197d69a96SAlexander Leidinger 					ch = c->parentchannel;
30287506547SAlexander Leidinger 			}
30387506547SAlexander Leidinger 		}
30487506547SAlexander Leidinger 		CHN_UNLOCK(c);
30587506547SAlexander Leidinger 	}
30697d69a96SAlexander Leidinger 	if (ch != NULL) {
30797d69a96SAlexander Leidinger 		CHN_LOCK(ch);
30897d69a96SAlexander Leidinger 		newspd = ch->speed;
30997d69a96SAlexander Leidinger 		CHN_UNLOCK(ch);
31087506547SAlexander Leidinger 	}
31187506547SAlexander Leidinger 	err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req);
31287506547SAlexander Leidinger 	if (err == 0 && req->newptr != NULL) {
31397d69a96SAlexander Leidinger 		if (ch == NULL || newspd < 1 ||
31497d69a96SAlexander Leidinger 				newspd < feeder_rate_ratemin ||
31597d69a96SAlexander Leidinger 				newspd > feeder_rate_ratemax)
31697d69a96SAlexander Leidinger 			return EINVAL;
31797d69a96SAlexander Leidinger 		if (pcm_inprog(d, 1) != 1) {
31897d69a96SAlexander Leidinger 			pcm_inprog(d, -1);
31997d69a96SAlexander Leidinger 			return EINPROGRESS;
32097d69a96SAlexander Leidinger 		}
32197d69a96SAlexander Leidinger 		CHN_LOCK(ch);
32297d69a96SAlexander Leidinger 		caps = chn_getcaps(ch);
32397d69a96SAlexander Leidinger 		if (caps == NULL || newspd < caps->minspeed ||
32497d69a96SAlexander Leidinger 				newspd > caps->maxspeed) {
32597d69a96SAlexander Leidinger 			CHN_UNLOCK(ch);
32687506547SAlexander Leidinger 			pcm_inprog(d, -1);
32787506547SAlexander Leidinger 			return EINVAL;
32887506547SAlexander Leidinger 		}
32997d69a96SAlexander Leidinger 		if (newspd != ch->speed) {
33097d69a96SAlexander Leidinger 			err = chn_setspeed(ch, newspd);
33197d69a96SAlexander Leidinger 			CHN_UNLOCK(ch);
33297d69a96SAlexander Leidinger 			if (err == 0) {
33387506547SAlexander Leidinger 				fake = pcm_getfakechan(d);
33487506547SAlexander Leidinger 				if (fake != NULL) {
33587506547SAlexander Leidinger 					CHN_LOCK(fake);
33687506547SAlexander Leidinger 					fake->speed = newspd;
33787506547SAlexander Leidinger 					CHN_UNLOCK(fake);
33887506547SAlexander Leidinger 				}
33987506547SAlexander Leidinger 			}
34097d69a96SAlexander Leidinger 		} else
34197d69a96SAlexander Leidinger 			CHN_UNLOCK(ch);
34287506547SAlexander Leidinger 		pcm_inprog(d, -1);
34397d69a96SAlexander Leidinger 	}
34487506547SAlexander Leidinger 	return err;
34587506547SAlexander Leidinger }
34687506547SAlexander Leidinger #endif
34787506547SAlexander Leidinger 
348285648f9SCameron Grant /* virtual channel interface */
349285648f9SCameron Grant 
350285648f9SCameron Grant int
351285648f9SCameron Grant vchan_create(struct pcm_channel *parent)
352285648f9SCameron Grant {
353285648f9SCameron Grant     	struct snddev_info *d = parent->parentsnddev;
354285648f9SCameron Grant 	struct pcmchan_children *pce;
35597d69a96SAlexander Leidinger 	struct pcm_channel *child, *fake;
35697d69a96SAlexander Leidinger 	struct pcmchan_caps *parent_caps;
35797d69a96SAlexander Leidinger 	int err, first, speed = 0;
35897d69a96SAlexander Leidinger 
35997d69a96SAlexander Leidinger 	if (!(parent->flags & CHN_F_BUSY))
36097d69a96SAlexander Leidinger 		return EBUSY;
36197d69a96SAlexander Leidinger 
362285648f9SCameron Grant 
36312e524a2SDon Lewis 	CHN_UNLOCK(parent);
36412e524a2SDon Lewis 
365a163d034SWarner Losh 	pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO);
36649c5e6e2SCameron Grant 	if (!pce) {
36712e524a2SDon Lewis    		CHN_LOCK(parent);
368285648f9SCameron Grant 		return ENOMEM;
36949c5e6e2SCameron Grant 	}
370285648f9SCameron Grant 
371285648f9SCameron Grant 	/* create a new playback channel */
372285648f9SCameron Grant 	child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent);
373285648f9SCameron Grant 	if (!child) {
374285648f9SCameron Grant 		free(pce, M_DEVBUF);
37512e524a2SDon Lewis    		CHN_LOCK(parent);
376285648f9SCameron Grant 		return ENODEV;
377285648f9SCameron Grant 	}
378285648f9SCameron Grant 	pce->channel = child;
379285648f9SCameron Grant 
380285648f9SCameron Grant 	/* add us to our grandparent's channel list */
3815ee30e27SMathew Kanner 	/*
3825ee30e27SMathew Kanner 	 * XXX maybe we shouldn't always add the dev_t
3835ee30e27SMathew Kanner  	 */
3845ee30e27SMathew Kanner 	err = pcm_chn_add(d, child);
385285648f9SCameron Grant 	if (err) {
386285648f9SCameron Grant 		pcm_chn_destroy(child);
387285648f9SCameron Grant 		free(pce, M_DEVBUF);
38897d69a96SAlexander Leidinger 		CHN_LOCK(parent);
38997d69a96SAlexander Leidinger 		return err;
390285648f9SCameron Grant 	}
391285648f9SCameron Grant 
39212e524a2SDon Lewis    	CHN_LOCK(parent);
39397d69a96SAlexander Leidinger 	/* add us to our parent channel's children */
39497d69a96SAlexander Leidinger 	first = SLIST_EMPTY(&parent->children);
39597d69a96SAlexander Leidinger 	SLIST_INSERT_HEAD(&parent->children, pce, link);
39697d69a96SAlexander Leidinger 	parent->flags |= CHN_F_HAS_VCHAN;
39787506547SAlexander Leidinger 
39897d69a96SAlexander Leidinger 	if (first) {
39997d69a96SAlexander Leidinger 		parent_caps = chn_getcaps(parent);
40097d69a96SAlexander Leidinger 		if (parent_caps == NULL)
40197d69a96SAlexander Leidinger 			err = EINVAL;
40297d69a96SAlexander Leidinger 
40397d69a96SAlexander Leidinger 		if (!err)
404285648f9SCameron Grant 			err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE);
40587506547SAlexander Leidinger 
40697d69a96SAlexander Leidinger 		if (!err) {
40787506547SAlexander Leidinger 			fake = pcm_getfakechan(d);
40887506547SAlexander Leidinger 			if (fake != NULL) {
40987506547SAlexander Leidinger 				/*
41097d69a96SAlexander Leidinger 				 * Avoid querying kernel hint, use saved value
41197d69a96SAlexander Leidinger 				 * from fake channel.
41287506547SAlexander Leidinger 				 */
41397d69a96SAlexander Leidinger 				CHN_UNLOCK(parent);
41487506547SAlexander Leidinger 				CHN_LOCK(fake);
41587506547SAlexander Leidinger 				speed = fake->speed;
41687506547SAlexander Leidinger 				CHN_UNLOCK(fake);
41797d69a96SAlexander Leidinger 				CHN_LOCK(parent);
41887506547SAlexander Leidinger 			}
41997d69a96SAlexander Leidinger 
42087506547SAlexander Leidinger 			/*
42187506547SAlexander Leidinger 			 * This is very sad. Few soundcards advertised as being
42287506547SAlexander Leidinger 			 * able to do (insanely) higher/lower speed, but in
42387506547SAlexander Leidinger 			 * reality, they simply can't. At least, we give user chance
42497d69a96SAlexander Leidinger 			 * to set sane value via kernel hints or sysctl.
42587506547SAlexander Leidinger 			 */
42697d69a96SAlexander Leidinger 			if (speed < 1) {
42797d69a96SAlexander Leidinger 				int r;
42897d69a96SAlexander Leidinger 				CHN_UNLOCK(parent);
42997d69a96SAlexander Leidinger 				r = resource_int_value(device_get_name(parent->dev),
43087506547SAlexander Leidinger 							device_get_unit(parent->dev),
43197d69a96SAlexander Leidinger 								"vchanrate", &speed);
43297d69a96SAlexander Leidinger 				CHN_LOCK(parent);
43397d69a96SAlexander Leidinger 				if (r != 0)
43487506547SAlexander Leidinger 					speed = VCHAN_DEFAULT_SPEED;
43587506547SAlexander Leidinger 			}
43687506547SAlexander Leidinger 
43787506547SAlexander Leidinger 			/*
43887506547SAlexander Leidinger 			 * Limit speed based on driver caps.
43987506547SAlexander Leidinger 			 * This is supposed to help fixed rate, non-VRA
44087506547SAlexander Leidinger 			 * AC97 cards, but.. (see below)
44187506547SAlexander Leidinger 			 */
44287506547SAlexander Leidinger 			if (speed < parent_caps->minspeed)
44387506547SAlexander Leidinger 				speed = parent_caps->minspeed;
44487506547SAlexander Leidinger 			if (speed > parent_caps->maxspeed)
44587506547SAlexander Leidinger 				speed = parent_caps->maxspeed;
44687506547SAlexander Leidinger 
44787506547SAlexander Leidinger 			/*
44887506547SAlexander Leidinger 			 * We still need to limit the speed between
44987506547SAlexander Leidinger 			 * feeder_rate_ratemin <-> feeder_rate_ratemax. This is
45087506547SAlexander Leidinger 			 * just an escape goat if all of the above failed
45187506547SAlexander Leidinger 			 * miserably.
45287506547SAlexander Leidinger 			 */
45387506547SAlexander Leidinger 			if (speed < feeder_rate_ratemin)
45487506547SAlexander Leidinger 				speed = feeder_rate_ratemin;
45587506547SAlexander Leidinger 			if (speed > feeder_rate_ratemax)
45687506547SAlexander Leidinger 				speed = feeder_rate_ratemax;
45787506547SAlexander Leidinger 
45887506547SAlexander Leidinger 			err = chn_setspeed(parent, speed);
45997d69a96SAlexander Leidinger 
46097d69a96SAlexander Leidinger 			if (!err && fake != NULL) {
46187506547SAlexander Leidinger 				/*
46287506547SAlexander Leidinger 				 * Save new value to fake channel.
46387506547SAlexander Leidinger 				 */
46497d69a96SAlexander Leidinger 				CHN_UNLOCK(parent);
46587506547SAlexander Leidinger 				CHN_LOCK(fake);
46687506547SAlexander Leidinger 				fake->speed = speed;
46787506547SAlexander Leidinger 				CHN_UNLOCK(fake);
46897d69a96SAlexander Leidinger 				CHN_LOCK(parent);
46987506547SAlexander Leidinger 			}
470285648f9SCameron Grant 		}
471285648f9SCameron Grant 
47297d69a96SAlexander Leidinger 		if (err) {
47397d69a96SAlexander Leidinger 			SLIST_REMOVE(&parent->children, pce, pcmchan_children, link);
47497d69a96SAlexander Leidinger 			parent->flags &= ~CHN_F_HAS_VCHAN;
47597d69a96SAlexander Leidinger 			CHN_UNLOCK(parent);
47697d69a96SAlexander Leidinger 			free(pce, M_DEVBUF);
47797d69a96SAlexander Leidinger 			pcm_chn_remove(d, child);
47897d69a96SAlexander Leidinger 			pcm_chn_destroy(child);
47997d69a96SAlexander Leidinger 			CHN_LOCK(parent);
480285648f9SCameron Grant 			return err;
481285648f9SCameron Grant 		}
48297d69a96SAlexander Leidinger 	}
48397d69a96SAlexander Leidinger 
48497d69a96SAlexander Leidinger 	return 0;
48597d69a96SAlexander Leidinger }
486285648f9SCameron Grant 
487285648f9SCameron Grant int
488285648f9SCameron Grant vchan_destroy(struct pcm_channel *c)
489285648f9SCameron Grant {
490285648f9SCameron Grant 	struct pcm_channel *parent = c->parentchannel;
491285648f9SCameron Grant     	struct snddev_info *d = parent->parentsnddev;
492285648f9SCameron Grant 	struct pcmchan_children *pce;
49397d69a96SAlexander Leidinger 	struct snddev_channel *sce;
494f637a36cSCameron Grant 	int err, last;
495285648f9SCameron Grant 
49649c5e6e2SCameron Grant 	CHN_LOCK(parent);
49749c5e6e2SCameron Grant 	if (!(parent->flags & CHN_F_BUSY)) {
49849c5e6e2SCameron Grant 		CHN_UNLOCK(parent);
49949c5e6e2SCameron Grant 		return EBUSY;
50049c5e6e2SCameron Grant 	}
50149c5e6e2SCameron Grant 	if (SLIST_EMPTY(&parent->children)) {
50249c5e6e2SCameron Grant 		CHN_UNLOCK(parent);
50349c5e6e2SCameron Grant 		return EINVAL;
50449c5e6e2SCameron Grant 	}
50549c5e6e2SCameron Grant 
506285648f9SCameron Grant 	/* remove us from our parent's children list */
507285648f9SCameron Grant 	SLIST_FOREACH(pce, &parent->children, link) {
508285648f9SCameron Grant 		if (pce->channel == c)
509285648f9SCameron Grant 			goto gotch;
510285648f9SCameron Grant 	}
51149c5e6e2SCameron Grant 	CHN_UNLOCK(parent);
512285648f9SCameron Grant 	return EINVAL;
513285648f9SCameron Grant gotch:
51497d69a96SAlexander Leidinger 	SLIST_FOREACH(sce, &d->channels, link) {
51597d69a96SAlexander Leidinger 		if (sce->channel == c) {
51697d69a96SAlexander Leidinger 			if (sce->dsp_devt)
51797d69a96SAlexander Leidinger 				destroy_dev(sce->dsp_devt);
51897d69a96SAlexander Leidinger 			if (sce->dspW_devt)
51997d69a96SAlexander Leidinger 				destroy_dev(sce->dspW_devt);
52097d69a96SAlexander Leidinger 			if (sce->audio_devt)
52197d69a96SAlexander Leidinger 				destroy_dev(sce->audio_devt);
52297d69a96SAlexander Leidinger 			if (sce->dspr_devt)
52397d69a96SAlexander Leidinger 				destroy_dev(sce->dspr_devt);
52497d69a96SAlexander Leidinger 			break;
52597d69a96SAlexander Leidinger 		}
52697d69a96SAlexander Leidinger 	}
527285648f9SCameron Grant 	SLIST_REMOVE(&parent->children, pce, pcmchan_children, link);
528285648f9SCameron Grant 	free(pce, M_DEVBUF);
529285648f9SCameron Grant 
530f637a36cSCameron Grant 	last = SLIST_EMPTY(&parent->children);
53197d69a96SAlexander Leidinger 	if (last) {
532285648f9SCameron Grant 		parent->flags &= ~CHN_F_BUSY;
53397d69a96SAlexander Leidinger 		parent->flags &= ~CHN_F_HAS_VCHAN;
53497d69a96SAlexander Leidinger 	}
535f637a36cSCameron Grant 
53645550658SPoul-Henning Kamp 	/* remove us from our grandparent's channel list */
5375ee30e27SMathew Kanner 	err = pcm_chn_remove(d, c);
538f637a36cSCameron Grant 
53949c5e6e2SCameron Grant 	CHN_UNLOCK(parent);
540285648f9SCameron Grant 	/* destroy ourselves */
54197d69a96SAlexander Leidinger 	if (!err)
542285648f9SCameron Grant 		err = pcm_chn_destroy(c);
543285648f9SCameron Grant 
54497d69a96SAlexander Leidinger #if 0
54597d69a96SAlexander Leidinger 	if (!err && last) {
54697d69a96SAlexander Leidinger 		CHN_LOCK(parent);
54797d69a96SAlexander Leidinger 		chn_reset(parent, chn_getcaps(parent)->fmtlist[0]);
54897d69a96SAlexander Leidinger 		chn_setspeed(parent, chn_getcaps(parent)->minspeed);
54997d69a96SAlexander Leidinger 		CHN_UNLOCK(parent);
55097d69a96SAlexander Leidinger 	}
55197d69a96SAlexander Leidinger #endif
55297d69a96SAlexander Leidinger 
553285648f9SCameron Grant 	return err;
554285648f9SCameron Grant }
555285648f9SCameron Grant 
556285648f9SCameron Grant int
55767b1dce3SCameron Grant vchan_initsys(device_t dev)
558285648f9SCameron Grant {
559285648f9SCameron Grant #ifdef SND_DYNSYSCTL
56067b1dce3SCameron Grant 	struct snddev_info *d;
56167b1dce3SCameron Grant 
56267b1dce3SCameron Grant     	d = device_get_softc(dev);
56367b1dce3SCameron Grant 	SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
564285648f9SCameron Grant             OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
56564d85ef7SMark Murray 	    sysctl_hw_snd_vchans, "I", "");
56687506547SAlexander Leidinger 	SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
56787506547SAlexander Leidinger 	    OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
56887506547SAlexander Leidinger 	    sysctl_hw_snd_vchanrate, "I", "");
569285648f9SCameron Grant #endif
57067b1dce3SCameron Grant 
571285648f9SCameron Grant 	return 0;
572285648f9SCameron Grant }
573