summaryrefslogtreecommitdiffstats
path: root/sys/dev/sound
diff options
context:
space:
mode:
authorhselasky <hselasky@FreeBSD.org>2015-03-21 09:45:45 +0000
committerhselasky <hselasky@FreeBSD.org>2015-03-21 09:45:45 +0000
commit355e724a9a41bcc6a4b2ff6ed40f491e1e6768e3 (patch)
tree58e89a24a3c457190cd1d0c1690cd5211e093d94 /sys/dev/sound
parent3a38addca2bf19f4785c67fc295314e91a6cfd30 (diff)
downloadFreeBSD-src-355e724a9a41bcc6a4b2ff6ed40f491e1e6768e3.zip
FreeBSD-src-355e724a9a41bcc6a4b2ff6ed40f491e1e6768e3.tar.gz
The synchronisation value returned by the so-called feedback endpoint
appears to be too inaccurate that it can be used to synchronize the playback data stream. If there is a recording endpoint associated with the playback endpoint, use that instead. That means if the isochronous OUT endpoint is asynchronus the USB audio driver will automatically start recording, if possible, to get exact information about the needed sample rate adjustments. In no recording endpoint is present, no rate adaption will be done. While at it fix an issue where the hardware buffer pointers don't get reset at the first device PCM trigger. Make some variables 32-bit to avoid problems with multithreading. MFC after: 3 days PR: 198444
Diffstat (limited to 'sys/dev/sound')
-rw-r--r--sys/dev/sound/usb/uaudio.c390
-rw-r--r--sys/dev/sound/usb/uaudio.h4
-rw-r--r--sys/dev/sound/usb/uaudio_pcm.c14
3 files changed, 277 insertions, 131 deletions
diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c
index 5f11bba..d99002e 100644
--- a/sys/dev/sound/usb/uaudio.c
+++ b/sys/dev/sound/usb/uaudio.c
@@ -212,26 +212,25 @@ struct uaudio_chan {
uint32_t sample_rem;
uint32_t sample_curr;
uint32_t max_buf;
+ int32_t jitter_rem;
+ int32_t jitter_curr;
+
+ int feedback_rate;
uint32_t pcm_format[2];
uint16_t bytes_per_frame[2];
- uint8_t num_alt;
- uint8_t cur_alt;
- uint8_t set_alt;
- uint8_t operation;
+ uint32_t intr_counter;
+ uint32_t running;
+ uint32_t num_alt;
+ uint32_t cur_alt;
+ uint32_t set_alt;
+ uint32_t operation;
#define CHAN_OP_NONE 0
#define CHAN_OP_START 1
#define CHAN_OP_STOP 2
#define CHAN_OP_DRAIN 3
-
- /* USB audio feedback endpoint state */
- struct {
- uint16_t time; /* I/O interrupt count */
- int16_t constant; /* sample rate adjustment in Hz */
- int16_t remainder; /* current remainder */
- } feedback;
};
#define UMIDI_EMB_JACK_MAX 16 /* units */
@@ -1090,6 +1089,11 @@ uaudio_attach_sub(device_t dev, kobj_class_t mixer_class, kobj_class_t chan_clas
uaudio_mixer_register_sysctl(sc, dev);
+ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
+ "feedback_rate", CTLFLAG_RD, &sc->sc_play_chan.feedback_rate,
+ 0, "Feedback sample rate in Hz");
+
return (0); /* success */
detach:
@@ -1288,7 +1292,6 @@ uaudio_configure_msg_sub(struct uaudio_softc *sc,
chan->frames_per_second = fps;
chan->sample_rem = chan_alt->sample_rate % fps;
chan->sample_curr = 0;
- chan->frames_per_second = fps;
/* compute required buffer size */
buf_size = (chan->bytes_per_frame[1] * frames);
@@ -1968,7 +1971,7 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error)
{
struct uaudio_chan *ch = usbd_xfer_softc(xfer);
struct usb_page_cache *pc;
- uint64_t sample_rate = ch->usb_alt[ch->cur_alt].sample_rate;
+ uint64_t sample_rate;
uint8_t buf[4];
uint64_t temp;
int len;
@@ -2011,6 +2014,8 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error)
temp *= 125ULL;
+ sample_rate = ch->usb_alt[ch->cur_alt].sample_rate;
+
/* auto adjust */
while (temp < (sample_rate - (sample_rate / 4)))
temp *= 2;
@@ -2018,35 +2023,10 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error)
while (temp > (sample_rate + (sample_rate / 2)))
temp /= 2;
- /*
- * Some USB audio devices only report a sample rate
- * different from the nominal one when they want one
- * more or less sample. Make sure we catch this case
- * by pulling the sample rate offset slowly towards
- * zero if the reported value is equal to the sample
- * rate.
- */
- if (temp > sample_rate)
- ch->feedback.constant += 1;
- else if (temp < sample_rate)
- ch->feedback.constant -= 1;
- else if (ch->feedback.constant > 0)
- ch->feedback.constant--;
- else if (ch->feedback.constant < 0)
- ch->feedback.constant++;
-
- DPRINTF("Comparing %d Hz :: %d Hz :: %d samples drift\n",
- (int)temp, (int)sample_rate, (int)ch->feedback.constant);
+ DPRINTF("Comparing %d Hz :: %d Hz\n",
+ (int)temp, (int)sample_rate);
- /*
- * Range check sync constant. We cannot change the
- * number of samples per second by more than the value
- * defined by "UAUDIO_IRQS":
- */
- if (ch->feedback.constant > UAUDIO_IRQS)
- ch->feedback.constant = UAUDIO_IRQS;
- else if (ch->feedback.constant < -UAUDIO_IRQS)
- ch->feedback.constant = -UAUDIO_IRQS;
+ ch->feedback_rate = temp;
break;
case USB_ST_SETUP:
@@ -2060,43 +2040,98 @@ uaudio_chan_play_sync_callback(struct usb_xfer *xfer, usb_error_t error)
}
}
+static int
+uaudio_chan_is_async(struct uaudio_chan *ch, uint8_t alt)
+{
+ uint8_t attr = ch->usb_alt[alt].p_ed1->bmAttributes;
+ return (UE_GET_ISO_TYPE(attr) == UE_ISO_ASYNC);
+}
+
static void
uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error)
{
struct uaudio_chan *ch = usbd_xfer_softc(xfer);
+ struct uaudio_chan *ch_rec;
struct usb_page_cache *pc;
- uint32_t sample_size = ch->usb_alt[ch->cur_alt].sample_size;
uint32_t mfl;
uint32_t total;
uint32_t blockcount;
uint32_t n;
uint32_t offset;
+ int sample_size;
int actlen;
int sumlen;
- usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
-
- if (ch->end == ch->start) {
- DPRINTF("no buffer!\n");
+ if (ch->running == 0 || ch->start == ch->end) {
+ DPRINTF("not running or no buffer!\n");
return;
}
+ /* check if there is a record channel */
+ if (ch->priv_sc->sc_rec_chan.num_alt > 0)
+ ch_rec = &ch->priv_sc->sc_rec_chan;
+ else
+ ch_rec = NULL;
+
+ usbd_xfer_status(xfer, &actlen, &sumlen, NULL, NULL);
+
switch (USB_GET_STATE(xfer)) {
+ case USB_ST_SETUP:
+tr_setup:
+ if (ch_rec != NULL) {
+ /* reset receive jitter counters */
+ mtx_lock(ch_rec->pcm_mtx);
+ ch_rec->jitter_curr = 0;
+ ch_rec->jitter_rem = 0;
+ mtx_unlock(ch_rec->pcm_mtx);
+ }
+
+ /* reset transmit jitter counters */
+ ch->jitter_curr = 0;
+ ch->jitter_rem = 0;
+
+ /* FALLTHROUGH */
case USB_ST_TRANSFERRED:
-tr_transferred:
if (actlen < sumlen) {
DPRINTF("short transfer, "
"%d of %d bytes\n", actlen, sumlen);
}
chn_intr(ch->pcm_ch);
+ /*
+ * Check for asynchronous playback endpoint and that
+ * the playback endpoint is properly configured:
+ */
+ if (ch_rec != NULL &&
+ uaudio_chan_is_async(ch, ch->cur_alt) != 0) {
+ mtx_lock(ch_rec->pcm_mtx);
+ if (ch_rec->cur_alt < ch_rec->num_alt) {
+ int64_t tx_jitter;
+ int64_t rx_rate;
+
+ /* translate receive jitter into transmit jitter */
+ tx_jitter = ch->usb_alt[ch->cur_alt].sample_rate;
+ tx_jitter = (tx_jitter * ch_rec->jitter_curr) +
+ ch->jitter_rem;
+
+ /* reset receive jitter counters */
+ ch_rec->jitter_curr = 0;
+ ch_rec->jitter_rem = 0;
+
+ /* compute exact number of transmit jitter samples */
+ rx_rate = ch_rec->usb_alt[ch_rec->cur_alt].sample_rate;
+ ch->jitter_curr += tx_jitter / rx_rate;
+ ch->jitter_rem = tx_jitter % rx_rate;
+ }
+ mtx_unlock(ch_rec->pcm_mtx);
+ }
+
/* start the SYNC transfer one time per second, if any */
- if (++(ch->feedback.time) >= UAUDIO_IRQS) {
- ch->feedback.time = 0;
+ if (++(ch->intr_counter) >= UAUDIO_IRQS) {
+ ch->intr_counter = 0;
usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]);
}
- case USB_ST_SETUP:
mfl = usbd_xfer_max_framelen(xfer);
if (ch->bytes_per_frame[1] > mfl) {
@@ -2112,6 +2147,9 @@ tr_transferred:
/* setup number of frames */
usbd_xfer_set_frames(xfer, blockcount);
+ /* get sample size */
+ sample_size = ch->usb_alt[ch->cur_alt].sample_size;
+
/* reset total length */
total = 0;
@@ -2127,31 +2165,23 @@ tr_transferred:
frame_len = ch->bytes_per_frame[0];
}
- if (n == (blockcount - 1)) {
- /*
- * Update sync remainder and check if
- * we should transmit more or less
- * data:
- */
- ch->feedback.remainder += ch->feedback.constant;
- if (ch->feedback.remainder >= UAUDIO_IRQS) {
- ch->feedback.remainder -= UAUDIO_IRQS;
- DPRINTFN(6, "sending one sample more\n");
- if ((frame_len + sample_size) <= mfl)
- frame_len += sample_size;
- } else if (ch->feedback.remainder <= -UAUDIO_IRQS) {
- ch->feedback.remainder += UAUDIO_IRQS;
- DPRINTFN(6, "sending one sample less\n");
- if (frame_len >= sample_size)
- frame_len -= sample_size;
- }
+ /* handle free running clock case */
+ if (ch->jitter_curr > 0 &&
+ (frame_len + sample_size) <= mfl) {
+ DPRINTFN(6, "sending one sample more\n");
+ ch->jitter_curr--;
+ frame_len += sample_size;
+ } else if (ch->jitter_curr < 0 &&
+ frame_len >= sample_size) {
+ DPRINTFN(6, "sending one sample less\n");
+ ch->jitter_curr++;
+ frame_len -= sample_size;
}
-
usbd_xfer_set_frame_len(xfer, n, frame_len);
total += frame_len;
}
- DPRINTFN(6, "transfer %d bytes\n", total);
+ DPRINTFN(6, "transferring %d bytes\n", total);
offset = 0;
@@ -2159,28 +2189,25 @@ tr_transferred:
while (total > 0) {
n = (ch->end - ch->cur);
- if (n > total) {
+ if (n > total)
n = total;
- }
+
usbd_copy_in(pc, offset, ch->cur, n);
total -= n;
ch->cur += n;
offset += n;
- if (ch->cur >= ch->end) {
+ if (ch->cur >= ch->end)
ch->cur = ch->start;
- }
}
-
usbd_transfer_submit(xfer);
break;
default: /* Error */
- if (error == USB_ERR_CANCELLED) {
- break;
- }
- goto tr_transferred;
+ if (error != USB_ERR_CANCELLED)
+ goto tr_setup;
+ break;
}
}
@@ -2196,36 +2223,59 @@ uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error)
struct uaudio_chan *ch = usbd_xfer_softc(xfer);
struct usb_page_cache *pc;
uint32_t offset0;
- uint32_t offset1;
uint32_t mfl;
int m;
int n;
int len;
int actlen;
int nframes;
- int blockcount;
-
- usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
- mfl = usbd_xfer_max_framelen(xfer);
+ int expected_bytes;
+ int sample_size;
- if (ch->end == ch->start) {
+ if (ch->start == ch->end) {
DPRINTF("no buffer!\n");
return;
}
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
+ mfl = usbd_xfer_max_framelen(xfer);
+
switch (USB_GET_STATE(xfer)) {
case USB_ST_TRANSFERRED:
- DPRINTFN(6, "transferred %d bytes\n", actlen);
-
offset0 = 0;
pc = usbd_xfer_get_frame(xfer, 0);
+ /* try to compute the number of expected bytes */
+ ch->sample_curr += (ch->sample_rem * ch->intr_frames);
+
+ /* compute number of expected bytes */
+ expected_bytes = (ch->intr_frames * ch->bytes_per_frame[0]) +
+ ((ch->sample_curr / ch->frames_per_second) *
+ (ch->bytes_per_frame[1] - ch->bytes_per_frame[0]));
+
+ /* keep remainder */
+ ch->sample_curr %= ch->frames_per_second;
+
+ /* get current sample size */
+ sample_size = ch->usb_alt[ch->cur_alt].sample_size;
+
for (n = 0; n != nframes; n++) {
+ uint32_t offset1 = offset0;
- offset1 = offset0;
len = usbd_xfer_frame_len(xfer, n);
+ /* make sure we only receive complete samples */
+ len = len - (len % sample_size);
+
+ /* subtract bytes received from expected payload */
+ expected_bytes -= len;
+
+ /* don't receive data when not ready */
+ if (ch->running == 0 || ch->cur_alt != ch->set_alt)
+ continue;
+
+ /* fill ring buffer with samples, if any */
while (len > 0) {
m = (ch->end - ch->cur);
@@ -2239,33 +2289,46 @@ uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error)
offset1 += m;
ch->cur += m;
- if (ch->cur >= ch->end) {
+ if (ch->cur >= ch->end)
ch->cur = ch->start;
- }
}
offset0 += mfl;
}
- chn_intr(ch->pcm_ch);
+ /* update current jitter */
+ ch->jitter_curr -= (expected_bytes / sample_size);
+
+ /* don't allow a huge amount of jitter to accumulate */
+ nframes = 2 * ch->intr_frames;
+
+ /* range check current jitter */
+ if (ch->jitter_curr < -nframes)
+ ch->jitter_curr = -nframes;
+ else if (ch->jitter_curr > nframes)
+ ch->jitter_curr = nframes;
+
+ DPRINTFN(6, "transferred %d bytes, jitter %d samples\n",
+ actlen, ch->jitter_curr);
+
+ if (ch->running != 0)
+ chn_intr(ch->pcm_ch);
case USB_ST_SETUP:
tr_setup:
- blockcount = ch->intr_frames;
+ nframes = ch->intr_frames;
- usbd_xfer_set_frames(xfer, blockcount);
- for (n = 0; n < blockcount; n++) {
+ usbd_xfer_set_frames(xfer, nframes);
+ for (n = 0; n != nframes; n++)
usbd_xfer_set_frame_len(xfer, n, mfl);
- }
usbd_transfer_submit(xfer);
break;
default: /* Error */
- if (error == USB_ERR_CANCELLED) {
- break;
- }
- goto tr_setup;
+ if (error != USB_ERR_CANCELLED)
+ goto tr_setup;
+ break;
}
}
@@ -2338,13 +2401,7 @@ int
uaudio_chan_set_param_blocksize(struct uaudio_chan *ch, uint32_t blocksize)
{
uint32_t temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt);
-
sndbuf_setup(ch->pcm_buf, ch->buf, temp);
-
- ch->start = ch->buf;
- ch->end = ch->buf + temp;
- ch->cur = ch->buf;
-
return (temp / 2);
}
@@ -2358,8 +2415,11 @@ uaudio_chan_set_param_fragments(struct uaudio_chan *ch, uint32_t blocksize,
int
uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed)
{
+ struct uaudio_softc *sc;
uint8_t x;
+ sc = ch->priv_sc;
+
for (x = 0; x < ch->num_alt; x++) {
if (ch->usb_alt[x].sample_rate < speed) {
/* sample rate is too low */
@@ -2370,7 +2430,9 @@ uaudio_chan_set_param_speed(struct uaudio_chan *ch, uint32_t speed)
if (x != 0)
x--;
+ usb_proc_explore_lock(sc->sc_udev);
ch->set_alt = x;
+ usb_proc_explore_unlock(sc->sc_udev);
DPRINTF("Selecting alt %d\n", (int)x);
@@ -2441,16 +2503,16 @@ uaudio_chan_set_param_format(struct uaudio_chan *ch, uint32_t format)
return (0);
}
-int
-uaudio_chan_start(struct uaudio_chan *ch)
+static void
+uaudio_chan_start_sub(struct uaudio_chan *ch)
{
struct uaudio_softc *sc = ch->priv_sc;
int do_start = 0;
- usb_proc_explore_lock(sc->sc_udev);
if (ch->operation != CHAN_OP_DRAIN) {
if (ch->cur_alt == ch->set_alt &&
- ch->operation == CHAN_OP_NONE) {
+ ch->operation == CHAN_OP_NONE &&
+ mtx_owned(ch->pcm_mtx) != 0) {
/* save doing the explore task */
do_start = 1;
} else {
@@ -2459,28 +2521,81 @@ uaudio_chan_start(struct uaudio_chan *ch)
&sc->sc_config_msg[0], &sc->sc_config_msg[1]);
}
}
- usb_proc_explore_unlock(sc->sc_udev);
-
- /* reset feedback endpoint state */
- memset(&ch->feedback, 0, sizeof(ch->feedback));
-
if (do_start) {
usbd_transfer_start(ch->xfer[0]);
usbd_transfer_start(ch->xfer[1]);
}
- return (0);
}
-int
-uaudio_chan_stop(struct uaudio_chan *ch)
+static int
+uaudio_chan_need_both(struct uaudio_softc *sc)
+{
+ return (sc->sc_play_chan.num_alt > 0 &&
+ sc->sc_play_chan.running != 0 &&
+ uaudio_chan_is_async(&sc->sc_play_chan,
+ sc->sc_play_chan.set_alt) != 0 &&
+ sc->sc_rec_chan.num_alt > 0 &&
+ sc->sc_rec_chan.running == 0);
+}
+
+static int
+uaudio_chan_need_none(struct uaudio_softc *sc)
+{
+ return (sc->sc_play_chan.num_alt > 0 &&
+ sc->sc_play_chan.running == 0 &&
+ sc->sc_rec_chan.num_alt > 0 &&
+ sc->sc_rec_chan.running == 0);
+}
+
+void
+uaudio_chan_start(struct uaudio_chan *ch)
{
struct uaudio_softc *sc = ch->priv_sc;
- int do_stop = 0;
+ /* make operation atomic */
usb_proc_explore_lock(sc->sc_udev);
+
+ /* check if not running */
+ if (ch->running == 0) {
+ uint32_t temp;
+
+ /* get current buffer size */
+ temp = 2 * uaudio_get_buffer_size(ch, ch->set_alt);
+
+ /* set running flag */
+ ch->running = 1;
+
+ /* ensure the hardware buffer is reset */
+ ch->start = ch->buf;
+ ch->end = ch->buf + temp;
+ ch->cur = ch->buf;
+
+ if (uaudio_chan_need_both(sc)) {
+ /*
+ * Start both endpoints because of need for
+ * jitter information:
+ */
+ uaudio_chan_start_sub(&sc->sc_rec_chan);
+ uaudio_chan_start_sub(&sc->sc_play_chan);
+ } else {
+ uaudio_chan_start_sub(ch);
+ }
+ }
+
+ /* exit atomic operation */
+ usb_proc_explore_unlock(sc->sc_udev);
+}
+
+static void
+uaudio_chan_stop_sub(struct uaudio_chan *ch)
+{
+ struct uaudio_softc *sc = ch->priv_sc;
+ int do_stop = 0;
+
if (ch->operation != CHAN_OP_DRAIN) {
if (ch->cur_alt == ch->set_alt &&
- ch->operation == CHAN_OP_NONE) {
+ ch->operation == CHAN_OP_NONE &&
+ mtx_owned(ch->pcm_mtx) != 0) {
/* save doing the explore task */
do_stop = 1;
} else {
@@ -2489,13 +2604,44 @@ uaudio_chan_stop(struct uaudio_chan *ch)
&sc->sc_config_msg[0], &sc->sc_config_msg[1]);
}
}
- usb_proc_explore_unlock(sc->sc_udev);
-
if (do_stop) {
usbd_transfer_stop(ch->xfer[0]);
usbd_transfer_stop(ch->xfer[1]);
}
- return (0);
+}
+
+void
+uaudio_chan_stop(struct uaudio_chan *ch)
+{
+ struct uaudio_softc *sc = ch->priv_sc;
+
+ /* make operation atomic */
+ usb_proc_explore_lock(sc->sc_udev);
+
+ /* check if running */
+ if (ch->running != 0) {
+ /* clear running flag */
+ ch->running = 0;
+
+ if (uaudio_chan_need_both(sc)) {
+ /*
+ * Leave the endpoints running because we need
+ * information about jitter!
+ */
+ } else if (uaudio_chan_need_none(sc)) {
+ /*
+ * Stop both endpoints in case the one was used for
+ * jitter information:
+ */
+ uaudio_chan_stop_sub(&sc->sc_rec_chan);
+ uaudio_chan_stop_sub(&sc->sc_play_chan);
+ } else {
+ uaudio_chan_stop_sub(ch);
+ }
+ }
+
+ /* exit atomic operation */
+ usb_proc_explore_unlock(sc->sc_udev);
}
/*========================================================================*
diff --git a/sys/dev/sound/usb/uaudio.h b/sys/dev/sound/usb/uaudio.h
index 10c10de..2d9a875 100644
--- a/sys/dev/sound/usb/uaudio.h
+++ b/sys/dev/sound/usb/uaudio.h
@@ -54,8 +54,8 @@ extern struct pcmchan_matrix *uaudio_chan_getmatrix(struct uaudio_chan *ch,
uint32_t format);
extern int uaudio_chan_set_param_format(struct uaudio_chan *ch,
uint32_t format);
-extern int uaudio_chan_start(struct uaudio_chan *ch);
-extern int uaudio_chan_stop(struct uaudio_chan *ch);
+extern void uaudio_chan_start(struct uaudio_chan *ch);
+extern void uaudio_chan_stop(struct uaudio_chan *ch);
extern int uaudio_mixer_init_sub(struct uaudio_softc *sc,
struct snd_mixer *m);
extern int uaudio_mixer_uninit_sub(struct uaudio_softc *sc);
diff --git a/sys/dev/sound/usb/uaudio_pcm.c b/sys/dev/sound/usb/uaudio_pcm.c
index bd00a01..10e8cc4 100644
--- a/sys/dev/sound/usb/uaudio_pcm.c
+++ b/sys/dev/sound/usb/uaudio_pcm.c
@@ -81,14 +81,14 @@ ua_chan_setfragments(kobj_t obj, void *data, uint32_t blocksize, uint32_t blockc
static int
ua_chan_trigger(kobj_t obj, void *data, int go)
{
- if (!PCMTRIG_COMMON(go)) {
- return (0);
- }
- if (go == PCMTRIG_START) {
- return (uaudio_chan_start(data));
- } else {
- return (uaudio_chan_stop(data));
+ if (PCMTRIG_COMMON(go)) {
+ if (go == PCMTRIG_START) {
+ uaudio_chan_start(data);
+ } else {
+ uaudio_chan_stop(data);
+ }
}
+ return (0);
}
static uint32_t
OpenPOWER on IntegriCloud