xref: /freebsd/sys/dev/sound/pcm/vchan.c (revision d45d1f2077ce4c8052c6753c261b091af83e6ffc)
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);
199d45d1f20SAriff Abdullah 	CHN_LOCK(parent);
200d45d1f20SAriff Abdullah 	speed = sndbuf_getspd(parent->bufsoft);
201d45d1f20SAriff Abdullah 	CHN_UNLOCK(parent);
20212e524a2SDon Lewis 	CHN_LOCK(channel);
203285648f9SCameron Grant 	return speed;
204285648f9SCameron Grant }
205285648f9SCameron Grant 
206285648f9SCameron Grant static int
207285648f9SCameron Grant vchan_setblocksize(kobj_t obj, void *data, u_int32_t blocksize)
208285648f9SCameron Grant {
209285648f9SCameron Grant 	struct vchinfo *ch = data;
21097d69a96SAlexander Leidinger 	struct pcm_channel *channel = ch->channel;
211285648f9SCameron Grant 	struct pcm_channel *parent = ch->parent;
21212e524a2SDon Lewis 	/* struct pcm_channel *channel = ch->channel; */
21349c5e6e2SCameron Grant 	int prate, crate;
214285648f9SCameron Grant 
215285648f9SCameron Grant 	ch->blksz = blocksize;
21612e524a2SDon Lewis    	/* CHN_UNLOCK(channel); */
21797d69a96SAlexander Leidinger 	sndbuf_setblksz(channel->bufhard, blocksize);
218285648f9SCameron Grant 	chn_notify(parent, CHN_N_BLOCKSIZE);
21912e524a2SDon Lewis    	CHN_LOCK(parent);
22012e524a2SDon Lewis    	/* CHN_LOCK(channel); */
22149c5e6e2SCameron Grant 
22249c5e6e2SCameron Grant 	crate = ch->spd * ch->bps;
223d45d1f20SAriff Abdullah 	prate = sndbuf_getspd(parent->bufsoft) * sndbuf_getbps(parent->bufsoft);
224d45d1f20SAriff Abdullah 	blocksize = sndbuf_getblksz(parent->bufsoft);
22512e524a2SDon Lewis    	CHN_UNLOCK(parent);
22649c5e6e2SCameron Grant 	blocksize *= prate;
22749c5e6e2SCameron Grant 	blocksize /= crate;
22849c5e6e2SCameron Grant 
229285648f9SCameron Grant 	return blocksize;
230285648f9SCameron Grant }
231285648f9SCameron Grant 
232285648f9SCameron Grant static int
233285648f9SCameron Grant vchan_trigger(kobj_t obj, void *data, int go)
234285648f9SCameron Grant {
235285648f9SCameron Grant 	struct vchinfo *ch = data;
236285648f9SCameron Grant 	struct pcm_channel *parent = ch->parent;
23712e524a2SDon Lewis 	struct pcm_channel *channel = ch->channel;
238285648f9SCameron Grant 
239285648f9SCameron Grant 	if (go == PCMTRIG_EMLDMAWR || go == PCMTRIG_EMLDMARD)
240285648f9SCameron Grant 		return 0;
241285648f9SCameron Grant 
242285648f9SCameron Grant 	ch->run = (go == PCMTRIG_START)? 1 : 0;
24312e524a2SDon Lewis    	CHN_UNLOCK(channel);
244285648f9SCameron Grant 	chn_notify(parent, CHN_N_TRIGGER);
24512e524a2SDon Lewis    	CHN_LOCK(channel);
246285648f9SCameron Grant 
247285648f9SCameron Grant 	return 0;
248285648f9SCameron Grant }
249285648f9SCameron Grant 
250285648f9SCameron Grant static struct pcmchan_caps *
251285648f9SCameron Grant vchan_getcaps(kobj_t obj, void *data)
252285648f9SCameron Grant {
253285648f9SCameron Grant 	struct vchinfo *ch = data;
254285648f9SCameron Grant 
255d45d1f20SAriff Abdullah 	ch->caps.minspeed = sndbuf_getspd(ch->parent->bufsoft);
256285648f9SCameron Grant 	ch->caps.maxspeed = ch->caps.minspeed;
257285648f9SCameron Grant 	ch->caps.fmtlist = vchan_fmt;
258285648f9SCameron Grant 	ch->caps.caps = 0;
259285648f9SCameron Grant 
260285648f9SCameron Grant 	return &ch->caps;
261285648f9SCameron Grant }
262285648f9SCameron Grant 
263285648f9SCameron Grant static kobj_method_t vchan_methods[] = {
264285648f9SCameron Grant     	KOBJMETHOD(channel_init,		vchan_init),
265285648f9SCameron Grant     	KOBJMETHOD(channel_free,		vchan_free),
266285648f9SCameron Grant     	KOBJMETHOD(channel_setformat,		vchan_setformat),
267285648f9SCameron Grant     	KOBJMETHOD(channel_setspeed,		vchan_setspeed),
268285648f9SCameron Grant     	KOBJMETHOD(channel_setblocksize,	vchan_setblocksize),
269285648f9SCameron Grant     	KOBJMETHOD(channel_trigger,		vchan_trigger),
270285648f9SCameron Grant     	KOBJMETHOD(channel_getcaps,		vchan_getcaps),
271285648f9SCameron Grant 	{ 0, 0 }
272285648f9SCameron Grant };
273285648f9SCameron Grant CHANNEL_DECLARE(vchan);
274285648f9SCameron Grant 
27587506547SAlexander Leidinger /*
27687506547SAlexander Leidinger  * On the fly vchan rate settings
27787506547SAlexander Leidinger  */
27887506547SAlexander Leidinger #ifdef SND_DYNSYSCTL
27987506547SAlexander Leidinger static int
28087506547SAlexander Leidinger sysctl_hw_snd_vchanrate(SYSCTL_HANDLER_ARGS)
28187506547SAlexander Leidinger {
28287506547SAlexander Leidinger 	struct snddev_info *d;
28387506547SAlexander Leidinger     	struct snddev_channel *sce;
28497d69a96SAlexander Leidinger 	struct pcm_channel *c, *ch = NULL, *fake;
28587506547SAlexander Leidinger 	struct pcmchan_caps *caps;
28687506547SAlexander Leidinger 	int err = 0;
28787506547SAlexander Leidinger 	int newspd = 0;
28887506547SAlexander Leidinger 
28987506547SAlexander Leidinger 	d = oidp->oid_arg1;
29097d69a96SAlexander Leidinger 	if (!(d->flags & SD_F_AUTOVCHAN) || d->vchancount < 1)
29187506547SAlexander Leidinger 		return EINVAL;
29287506547SAlexander Leidinger 	SLIST_FOREACH(sce, &d->channels, link) {
29387506547SAlexander Leidinger 		c = sce->channel;
29487506547SAlexander Leidinger 		CHN_LOCK(c);
29587506547SAlexander Leidinger 		if (c->direction == PCMDIR_PLAY) {
29687506547SAlexander Leidinger 			if (c->flags & CHN_F_VIRTUAL) {
29787506547SAlexander Leidinger 				if (req->newptr != NULL &&
29887506547SAlexander Leidinger 						(c->flags & CHN_F_BUSY)) {
29987506547SAlexander Leidinger 					CHN_UNLOCK(c);
30087506547SAlexander Leidinger 					return EBUSY;
30187506547SAlexander Leidinger 				}
30297d69a96SAlexander Leidinger 				if (ch == NULL)
30397d69a96SAlexander Leidinger 					ch = c->parentchannel;
30487506547SAlexander Leidinger 			}
30587506547SAlexander Leidinger 		}
30687506547SAlexander Leidinger 		CHN_UNLOCK(c);
30787506547SAlexander Leidinger 	}
30897d69a96SAlexander Leidinger 	if (ch != NULL) {
30997d69a96SAlexander Leidinger 		CHN_LOCK(ch);
31097d69a96SAlexander Leidinger 		newspd = ch->speed;
31197d69a96SAlexander Leidinger 		CHN_UNLOCK(ch);
31287506547SAlexander Leidinger 	}
31387506547SAlexander Leidinger 	err = sysctl_handle_int(oidp, &newspd, sizeof(newspd), req);
31487506547SAlexander Leidinger 	if (err == 0 && req->newptr != NULL) {
31597d69a96SAlexander Leidinger 		if (ch == NULL || newspd < 1 ||
31697d69a96SAlexander Leidinger 				newspd < feeder_rate_ratemin ||
31797d69a96SAlexander Leidinger 				newspd > feeder_rate_ratemax)
31897d69a96SAlexander Leidinger 			return EINVAL;
31997d69a96SAlexander Leidinger 		if (pcm_inprog(d, 1) != 1) {
32097d69a96SAlexander Leidinger 			pcm_inprog(d, -1);
32197d69a96SAlexander Leidinger 			return EINPROGRESS;
32297d69a96SAlexander Leidinger 		}
32397d69a96SAlexander Leidinger 		CHN_LOCK(ch);
32497d69a96SAlexander Leidinger 		caps = chn_getcaps(ch);
32597d69a96SAlexander Leidinger 		if (caps == NULL || newspd < caps->minspeed ||
32697d69a96SAlexander Leidinger 				newspd > caps->maxspeed) {
32797d69a96SAlexander Leidinger 			CHN_UNLOCK(ch);
32887506547SAlexander Leidinger 			pcm_inprog(d, -1);
32987506547SAlexander Leidinger 			return EINVAL;
33087506547SAlexander Leidinger 		}
33197d69a96SAlexander Leidinger 		if (newspd != ch->speed) {
33297d69a96SAlexander Leidinger 			err = chn_setspeed(ch, newspd);
333d45d1f20SAriff Abdullah 			/*
334d45d1f20SAriff Abdullah 			 * Try to avoid FEEDER_RATE on parent channel if the
335d45d1f20SAriff Abdullah 			 * requested value is not supported by the hardware.
336d45d1f20SAriff Abdullah 			 */
337d45d1f20SAriff Abdullah 			if (!err && (ch->feederflags & (1 << FEEDER_RATE))) {
338d45d1f20SAriff Abdullah 				newspd = sndbuf_getspd(ch->bufhard);
339d45d1f20SAriff Abdullah 				err = chn_setspeed(ch, newspd);
340d45d1f20SAriff Abdullah 			}
34197d69a96SAlexander Leidinger 			CHN_UNLOCK(ch);
34297d69a96SAlexander Leidinger 			if (err == 0) {
34387506547SAlexander Leidinger 				fake = pcm_getfakechan(d);
34487506547SAlexander Leidinger 				if (fake != NULL) {
34587506547SAlexander Leidinger 					CHN_LOCK(fake);
34687506547SAlexander Leidinger 					fake->speed = newspd;
34787506547SAlexander Leidinger 					CHN_UNLOCK(fake);
34887506547SAlexander Leidinger 				}
34987506547SAlexander Leidinger 			}
35097d69a96SAlexander Leidinger 		} else
35197d69a96SAlexander Leidinger 			CHN_UNLOCK(ch);
35287506547SAlexander Leidinger 		pcm_inprog(d, -1);
35397d69a96SAlexander Leidinger 	}
35487506547SAlexander Leidinger 	return err;
35587506547SAlexander Leidinger }
35687506547SAlexander Leidinger #endif
35787506547SAlexander Leidinger 
358285648f9SCameron Grant /* virtual channel interface */
359285648f9SCameron Grant 
360285648f9SCameron Grant int
361285648f9SCameron Grant vchan_create(struct pcm_channel *parent)
362285648f9SCameron Grant {
363285648f9SCameron Grant     	struct snddev_info *d = parent->parentsnddev;
364285648f9SCameron Grant 	struct pcmchan_children *pce;
36597d69a96SAlexander Leidinger 	struct pcm_channel *child, *fake;
36697d69a96SAlexander Leidinger 	struct pcmchan_caps *parent_caps;
36797d69a96SAlexander Leidinger 	int err, first, speed = 0;
36897d69a96SAlexander Leidinger 
36997d69a96SAlexander Leidinger 	if (!(parent->flags & CHN_F_BUSY))
37097d69a96SAlexander Leidinger 		return EBUSY;
37197d69a96SAlexander Leidinger 
372285648f9SCameron Grant 
37312e524a2SDon Lewis 	CHN_UNLOCK(parent);
37412e524a2SDon Lewis 
375a163d034SWarner Losh 	pce = malloc(sizeof(*pce), M_DEVBUF, M_WAITOK | M_ZERO);
37649c5e6e2SCameron Grant 	if (!pce) {
37712e524a2SDon Lewis    		CHN_LOCK(parent);
378285648f9SCameron Grant 		return ENOMEM;
37949c5e6e2SCameron Grant 	}
380285648f9SCameron Grant 
381285648f9SCameron Grant 	/* create a new playback channel */
382285648f9SCameron Grant 	child = pcm_chn_create(d, parent, &vchan_class, PCMDIR_VIRTUAL, parent);
383285648f9SCameron Grant 	if (!child) {
384285648f9SCameron Grant 		free(pce, M_DEVBUF);
38512e524a2SDon Lewis    		CHN_LOCK(parent);
386285648f9SCameron Grant 		return ENODEV;
387285648f9SCameron Grant 	}
388285648f9SCameron Grant 	pce->channel = child;
389285648f9SCameron Grant 
390285648f9SCameron Grant 	/* add us to our grandparent's channel list */
3915ee30e27SMathew Kanner 	/*
3925ee30e27SMathew Kanner 	 * XXX maybe we shouldn't always add the dev_t
3935ee30e27SMathew Kanner  	 */
3945ee30e27SMathew Kanner 	err = pcm_chn_add(d, child);
395285648f9SCameron Grant 	if (err) {
396285648f9SCameron Grant 		pcm_chn_destroy(child);
397285648f9SCameron Grant 		free(pce, M_DEVBUF);
39897d69a96SAlexander Leidinger 		CHN_LOCK(parent);
39997d69a96SAlexander Leidinger 		return err;
400285648f9SCameron Grant 	}
401285648f9SCameron Grant 
40212e524a2SDon Lewis    	CHN_LOCK(parent);
40397d69a96SAlexander Leidinger 	/* add us to our parent channel's children */
40497d69a96SAlexander Leidinger 	first = SLIST_EMPTY(&parent->children);
40597d69a96SAlexander Leidinger 	SLIST_INSERT_HEAD(&parent->children, pce, link);
40697d69a96SAlexander Leidinger 	parent->flags |= CHN_F_HAS_VCHAN;
40787506547SAlexander Leidinger 
40897d69a96SAlexander Leidinger 	if (first) {
40997d69a96SAlexander Leidinger 		parent_caps = chn_getcaps(parent);
41097d69a96SAlexander Leidinger 		if (parent_caps == NULL)
41197d69a96SAlexander Leidinger 			err = EINVAL;
41297d69a96SAlexander Leidinger 
41397d69a96SAlexander Leidinger 		if (!err)
414285648f9SCameron Grant 			err = chn_reset(parent, AFMT_STEREO | AFMT_S16_LE);
41587506547SAlexander Leidinger 
41697d69a96SAlexander Leidinger 		if (!err) {
41787506547SAlexander Leidinger 			fake = pcm_getfakechan(d);
41887506547SAlexander Leidinger 			if (fake != NULL) {
41987506547SAlexander Leidinger 				/*
42097d69a96SAlexander Leidinger 				 * Avoid querying kernel hint, use saved value
42197d69a96SAlexander Leidinger 				 * from fake channel.
42287506547SAlexander Leidinger 				 */
42397d69a96SAlexander Leidinger 				CHN_UNLOCK(parent);
42487506547SAlexander Leidinger 				CHN_LOCK(fake);
42587506547SAlexander Leidinger 				speed = fake->speed;
42687506547SAlexander Leidinger 				CHN_UNLOCK(fake);
42797d69a96SAlexander Leidinger 				CHN_LOCK(parent);
42887506547SAlexander Leidinger 			}
42997d69a96SAlexander Leidinger 
43087506547SAlexander Leidinger 			/*
43187506547SAlexander Leidinger 			 * This is very sad. Few soundcards advertised as being
43287506547SAlexander Leidinger 			 * able to do (insanely) higher/lower speed, but in
43387506547SAlexander Leidinger 			 * reality, they simply can't. At least, we give user chance
43497d69a96SAlexander Leidinger 			 * to set sane value via kernel hints or sysctl.
43587506547SAlexander Leidinger 			 */
43697d69a96SAlexander Leidinger 			if (speed < 1) {
43797d69a96SAlexander Leidinger 				int r;
43897d69a96SAlexander Leidinger 				CHN_UNLOCK(parent);
43997d69a96SAlexander Leidinger 				r = resource_int_value(device_get_name(parent->dev),
44087506547SAlexander Leidinger 							device_get_unit(parent->dev),
44197d69a96SAlexander Leidinger 								"vchanrate", &speed);
44297d69a96SAlexander Leidinger 				CHN_LOCK(parent);
44397d69a96SAlexander Leidinger 				if (r != 0)
44487506547SAlexander Leidinger 					speed = VCHAN_DEFAULT_SPEED;
44587506547SAlexander Leidinger 			}
44687506547SAlexander Leidinger 
44787506547SAlexander Leidinger 			/*
44887506547SAlexander Leidinger 			 * Limit speed based on driver caps.
44987506547SAlexander Leidinger 			 * This is supposed to help fixed rate, non-VRA
45087506547SAlexander Leidinger 			 * AC97 cards, but.. (see below)
45187506547SAlexander Leidinger 			 */
45287506547SAlexander Leidinger 			if (speed < parent_caps->minspeed)
45387506547SAlexander Leidinger 				speed = parent_caps->minspeed;
45487506547SAlexander Leidinger 			if (speed > parent_caps->maxspeed)
45587506547SAlexander Leidinger 				speed = parent_caps->maxspeed;
45687506547SAlexander Leidinger 
45787506547SAlexander Leidinger 			/*
45887506547SAlexander Leidinger 			 * We still need to limit the speed between
45987506547SAlexander Leidinger 			 * feeder_rate_ratemin <-> feeder_rate_ratemax. This is
46087506547SAlexander Leidinger 			 * just an escape goat if all of the above failed
46187506547SAlexander Leidinger 			 * miserably.
46287506547SAlexander Leidinger 			 */
46387506547SAlexander Leidinger 			if (speed < feeder_rate_ratemin)
46487506547SAlexander Leidinger 				speed = feeder_rate_ratemin;
46587506547SAlexander Leidinger 			if (speed > feeder_rate_ratemax)
46687506547SAlexander Leidinger 				speed = feeder_rate_ratemax;
46787506547SAlexander Leidinger 
46887506547SAlexander Leidinger 			err = chn_setspeed(parent, speed);
469d45d1f20SAriff Abdullah 			/*
470d45d1f20SAriff Abdullah 			 * Try to avoid FEEDER_RATE on parent channel if the
471d45d1f20SAriff Abdullah 			 * requested value is not supported by the hardware.
472d45d1f20SAriff Abdullah 			 */
473d45d1f20SAriff Abdullah 			if (!err && (parent->feederflags & (1 << FEEDER_RATE))) {
474d45d1f20SAriff Abdullah 				speed = sndbuf_getspd(parent->bufhard);
475d45d1f20SAriff Abdullah 				err = chn_setspeed(parent, speed);
476d45d1f20SAriff Abdullah 			}
47797d69a96SAlexander Leidinger 
47897d69a96SAlexander Leidinger 			if (!err && fake != NULL) {
47987506547SAlexander Leidinger 				/*
48087506547SAlexander Leidinger 				 * Save new value to fake channel.
48187506547SAlexander Leidinger 				 */
48297d69a96SAlexander Leidinger 				CHN_UNLOCK(parent);
48387506547SAlexander Leidinger 				CHN_LOCK(fake);
48487506547SAlexander Leidinger 				fake->speed = speed;
48587506547SAlexander Leidinger 				CHN_UNLOCK(fake);
48697d69a96SAlexander Leidinger 				CHN_LOCK(parent);
48787506547SAlexander Leidinger 			}
488285648f9SCameron Grant 		}
489285648f9SCameron Grant 
49097d69a96SAlexander Leidinger 		if (err) {
49197d69a96SAlexander Leidinger 			SLIST_REMOVE(&parent->children, pce, pcmchan_children, link);
49297d69a96SAlexander Leidinger 			parent->flags &= ~CHN_F_HAS_VCHAN;
49397d69a96SAlexander Leidinger 			CHN_UNLOCK(parent);
49497d69a96SAlexander Leidinger 			free(pce, M_DEVBUF);
49597d69a96SAlexander Leidinger 			pcm_chn_remove(d, child);
49697d69a96SAlexander Leidinger 			pcm_chn_destroy(child);
49797d69a96SAlexander Leidinger 			CHN_LOCK(parent);
498285648f9SCameron Grant 			return err;
499285648f9SCameron Grant 		}
50097d69a96SAlexander Leidinger 	}
50197d69a96SAlexander Leidinger 
50297d69a96SAlexander Leidinger 	return 0;
50397d69a96SAlexander Leidinger }
504285648f9SCameron Grant 
505285648f9SCameron Grant int
506285648f9SCameron Grant vchan_destroy(struct pcm_channel *c)
507285648f9SCameron Grant {
508285648f9SCameron Grant 	struct pcm_channel *parent = c->parentchannel;
509285648f9SCameron Grant     	struct snddev_info *d = parent->parentsnddev;
510285648f9SCameron Grant 	struct pcmchan_children *pce;
51197d69a96SAlexander Leidinger 	struct snddev_channel *sce;
512f637a36cSCameron Grant 	int err, last;
513285648f9SCameron Grant 
51449c5e6e2SCameron Grant 	CHN_LOCK(parent);
51549c5e6e2SCameron Grant 	if (!(parent->flags & CHN_F_BUSY)) {
51649c5e6e2SCameron Grant 		CHN_UNLOCK(parent);
51749c5e6e2SCameron Grant 		return EBUSY;
51849c5e6e2SCameron Grant 	}
51949c5e6e2SCameron Grant 	if (SLIST_EMPTY(&parent->children)) {
52049c5e6e2SCameron Grant 		CHN_UNLOCK(parent);
52149c5e6e2SCameron Grant 		return EINVAL;
52249c5e6e2SCameron Grant 	}
52349c5e6e2SCameron Grant 
524285648f9SCameron Grant 	/* remove us from our parent's children list */
525285648f9SCameron Grant 	SLIST_FOREACH(pce, &parent->children, link) {
526285648f9SCameron Grant 		if (pce->channel == c)
527285648f9SCameron Grant 			goto gotch;
528285648f9SCameron Grant 	}
52949c5e6e2SCameron Grant 	CHN_UNLOCK(parent);
530285648f9SCameron Grant 	return EINVAL;
531285648f9SCameron Grant gotch:
53297d69a96SAlexander Leidinger 	SLIST_FOREACH(sce, &d->channels, link) {
53397d69a96SAlexander Leidinger 		if (sce->channel == c) {
53497d69a96SAlexander Leidinger 			if (sce->dsp_devt)
53597d69a96SAlexander Leidinger 				destroy_dev(sce->dsp_devt);
53697d69a96SAlexander Leidinger 			if (sce->dspW_devt)
53797d69a96SAlexander Leidinger 				destroy_dev(sce->dspW_devt);
53897d69a96SAlexander Leidinger 			if (sce->audio_devt)
53997d69a96SAlexander Leidinger 				destroy_dev(sce->audio_devt);
54097d69a96SAlexander Leidinger 			if (sce->dspr_devt)
54197d69a96SAlexander Leidinger 				destroy_dev(sce->dspr_devt);
54297d69a96SAlexander Leidinger 			break;
54397d69a96SAlexander Leidinger 		}
54497d69a96SAlexander Leidinger 	}
545285648f9SCameron Grant 	SLIST_REMOVE(&parent->children, pce, pcmchan_children, link);
546285648f9SCameron Grant 	free(pce, M_DEVBUF);
547285648f9SCameron Grant 
548f637a36cSCameron Grant 	last = SLIST_EMPTY(&parent->children);
54997d69a96SAlexander Leidinger 	if (last) {
550285648f9SCameron Grant 		parent->flags &= ~CHN_F_BUSY;
55197d69a96SAlexander Leidinger 		parent->flags &= ~CHN_F_HAS_VCHAN;
55297d69a96SAlexander Leidinger 	}
553f637a36cSCameron Grant 
55445550658SPoul-Henning Kamp 	/* remove us from our grandparent's channel list */
5555ee30e27SMathew Kanner 	err = pcm_chn_remove(d, c);
556f637a36cSCameron Grant 
55749c5e6e2SCameron Grant 	CHN_UNLOCK(parent);
558285648f9SCameron Grant 	/* destroy ourselves */
55997d69a96SAlexander Leidinger 	if (!err)
560285648f9SCameron Grant 		err = pcm_chn_destroy(c);
561285648f9SCameron Grant 
56297d69a96SAlexander Leidinger #if 0
56397d69a96SAlexander Leidinger 	if (!err && last) {
56497d69a96SAlexander Leidinger 		CHN_LOCK(parent);
56597d69a96SAlexander Leidinger 		chn_reset(parent, chn_getcaps(parent)->fmtlist[0]);
56697d69a96SAlexander Leidinger 		chn_setspeed(parent, chn_getcaps(parent)->minspeed);
56797d69a96SAlexander Leidinger 		CHN_UNLOCK(parent);
56897d69a96SAlexander Leidinger 	}
56997d69a96SAlexander Leidinger #endif
57097d69a96SAlexander Leidinger 
571285648f9SCameron Grant 	return err;
572285648f9SCameron Grant }
573285648f9SCameron Grant 
574285648f9SCameron Grant int
57567b1dce3SCameron Grant vchan_initsys(device_t dev)
576285648f9SCameron Grant {
577285648f9SCameron Grant #ifdef SND_DYNSYSCTL
57867b1dce3SCameron Grant 	struct snddev_info *d;
57967b1dce3SCameron Grant 
58067b1dce3SCameron Grant     	d = device_get_softc(dev);
58167b1dce3SCameron Grant 	SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
582285648f9SCameron Grant             OID_AUTO, "vchans", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
58364d85ef7SMark Murray 	    sysctl_hw_snd_vchans, "I", "");
58487506547SAlexander Leidinger 	SYSCTL_ADD_PROC(snd_sysctl_tree(dev), SYSCTL_CHILDREN(snd_sysctl_tree_top(dev)),
58587506547SAlexander Leidinger 	    OID_AUTO, "vchanrate", CTLTYPE_INT | CTLFLAG_RW, d, sizeof(d),
58687506547SAlexander Leidinger 	    sysctl_hw_snd_vchanrate, "I", "");
587285648f9SCameron Grant #endif
58867b1dce3SCameron Grant 
589285648f9SCameron Grant 	return 0;
590285648f9SCameron Grant }
591