summaryrefslogtreecommitdiffstats
path: root/sys/dev/sound/usb
diff options
context:
space:
mode:
authorhselasky <hselasky@FreeBSD.org>2012-10-28 14:37:17 +0000
committerhselasky <hselasky@FreeBSD.org>2012-10-28 14:37:17 +0000
commiteba8578ed36bb1ecaf0906655ad18d73c2e06f58 (patch)
treee34f4b22d394b10224b12956d323d3f5a195aeca /sys/dev/sound/usb
parent4d0fac96d4e65d77b510b85bbb11362ac85b505f (diff)
downloadFreeBSD-src-eba8578ed36bb1ecaf0906655ad18d73c2e06f58.zip
FreeBSD-src-eba8578ed36bb1ecaf0906655ad18d73c2e06f58.tar.gz
Implement support for the so-called USB feedback endpoint for USB
audio devices. This endpoint gives clues to the USB host about the actual data rate on asynchronous endpoints and makes the more expensive USB audio devices usable under FreeBSD. The Linux USB audio driver was used as reference for the automagic shift of the received value. MFC after: 1 week
Diffstat (limited to 'sys/dev/sound/usb')
-rw-r--r--sys/dev/sound/usb/uaudio.c182
1 files changed, 163 insertions, 19 deletions
diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c
index e9fce7c..c660677 100644
--- a/sys/dev/sound/usb/uaudio.c
+++ b/sys/dev/sound/usb/uaudio.c
@@ -176,7 +176,7 @@ struct uaudio_chan {
struct mtx *pcm_mtx; /* lock protecting this structure */
struct uaudio_softc *priv_sc;
struct pcm_channel *pcm_ch;
- struct usb_xfer *xfer[UAUDIO_NCHANBUFS];
+ struct usb_xfer *xfer[UAUDIO_NCHANBUFS + 1];
union uaudio_asf1d p_asf1d;
union uaudio_sed p_sed;
const usb_endpoint_descriptor_audio_t *p_ed1;
@@ -206,6 +206,12 @@ struct uaudio_chan {
uint8_t iface_index;
uint8_t iface_alt_index;
uint8_t channels;
+
+ uint8_t last_sync_time;
+ uint8_t last_sync_state;
+#define UAUDIO_SYNC_NONE 0
+#define UAUDIO_SYNC_MORE 1
+#define UAUDIO_SYNC_LESS 2
};
#define UMIDI_CABLES_MAX 16 /* units */
@@ -386,7 +392,9 @@ static device_attach_t uaudio_attach;
static device_detach_t uaudio_detach;
static usb_callback_t uaudio_chan_play_callback;
+static usb_callback_t uaudio_chan_play_sync_callback;
static usb_callback_t uaudio_chan_record_callback;
+static usb_callback_t uaudio_chan_record_sync_callback;
static usb_callback_t uaudio_mixer_write_cfg_callback;
static usb_callback_t umidi_bulk_read_callback;
static usb_callback_t umidi_bulk_write_callback;
@@ -482,7 +490,7 @@ static void uaudio_chan_dump_ep_desc(
#endif
static const struct usb_config
- uaudio_cfg_record[UAUDIO_NCHANBUFS] = {
+ uaudio_cfg_record[UAUDIO_NCHANBUFS + 1] = {
[0] = {
.type = UE_ISOCHRONOUS,
.endpoint = UE_ADDR_ANY,
@@ -502,10 +510,20 @@ static const struct usb_config
.flags = {.short_xfer_ok = 1,},
.callback = &uaudio_chan_record_callback,
},
+
+ [2] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_OUT,
+ .bufsize = 0, /* use "wMaxPacketSize * frames" */
+ .frames = 1,
+ .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,},
+ .callback = &uaudio_chan_record_sync_callback,
+ },
};
static const struct usb_config
- uaudio_cfg_play[UAUDIO_NCHANBUFS] = {
+ uaudio_cfg_play[UAUDIO_NCHANBUFS + 1] = {
[0] = {
.type = UE_ISOCHRONOUS,
.endpoint = UE_ADDR_ANY,
@@ -525,6 +543,16 @@ static const struct usb_config
.flags = {.short_xfer_ok = 1,},
.callback = &uaudio_chan_play_callback,
},
+
+ [2] = {
+ .type = UE_ISOCHRONOUS,
+ .endpoint = UE_ADDR_ANY,
+ .direction = UE_DIR_IN,
+ .bufsize = 0, /* use "wMaxPacketSize * frames" */
+ .frames = 1,
+ .flags = {.no_pipe_ok = 1,.short_xfer_ok = 1,},
+ .callback = &uaudio_chan_play_sync_callback,
+ },
};
static const struct usb_config
@@ -845,9 +873,9 @@ uaudio_detach(device_t dev)
* any.
*/
if (sc->sc_play_chan.valid)
- usbd_transfer_unsetup(sc->sc_play_chan.xfer, UAUDIO_NCHANBUFS);
+ usbd_transfer_unsetup(sc->sc_play_chan.xfer, UAUDIO_NCHANBUFS + 1);
if (sc->sc_rec_chan.valid)
- usbd_transfer_unsetup(sc->sc_rec_chan.xfer, UAUDIO_NCHANBUFS);
+ usbd_transfer_unsetup(sc->sc_rec_chan.xfer, UAUDIO_NCHANBUFS + 1);
if (bus_generic_detach(dev) != 0) {
DPRINTF("detach failed!\n");
@@ -1396,10 +1424,96 @@ done:
}
static void
+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;
+ uint8_t buf[4];
+ uint64_t temp;
+ int len;
+ int actlen;
+ int nframes;
+
+ usbd_xfer_status(xfer, &actlen, NULL, NULL, &nframes);
+
+ switch (USB_GET_STATE(xfer)) {
+ case USB_ST_TRANSFERRED:
+
+ DPRINTFN(6, "transferred %d bytes\n", actlen);
+
+ if (nframes == 0)
+ break;
+ len = usbd_xfer_frame_len(xfer, 0);
+ if (len == 0)
+ break;
+ if (len > sizeof(buf))
+ len = sizeof(buf);
+
+ memset(buf, 0, sizeof(buf));
+
+ pc = usbd_xfer_get_frame(xfer, 0);
+ usbd_copy_out(pc, 0, buf, len);
+
+ temp = UGETDW(buf);
+
+ DPRINTF("Value = 0x%08x\n", (int)temp);
+
+ /* auto-detect SYNC format */
+
+ if (len == 4)
+ temp &= 0x0fffffff;
+
+ /* check for no data */
+
+ if (temp == 0)
+ break;
+
+ /* correctly scale value */
+
+ temp = (temp * 125ULL) - 64;
+
+ /* auto adjust */
+
+ while (temp < (ch->sample_rate - (ch->sample_rate / 4)))
+ temp *= 2;
+
+ while (temp > (ch->sample_rate + (ch->sample_rate / 2)))
+ temp /= 2;
+
+ /* bias */
+
+ temp += (ch->sample_rate + 1999) / 2000;
+
+ /* compare */
+
+ DPRINTF("Comparing %d < %d\n",
+ (int)temp, (int)ch->sample_rate);
+
+ if (temp == ch->sample_rate)
+ ch->last_sync_state = UAUDIO_SYNC_NONE;
+ else if (temp > ch->sample_rate)
+ ch->last_sync_state = UAUDIO_SYNC_MORE;
+ else
+ ch->last_sync_state = UAUDIO_SYNC_LESS;
+ break;
+
+ case USB_ST_SETUP:
+ usbd_xfer_set_frames(xfer, 1);
+ usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_framelen(xfer));
+ usbd_transfer_submit(xfer);
+ break;
+
+ default: /* Error */
+ break;
+ }
+}
+
+static void
uaudio_chan_play_callback(struct usb_xfer *xfer, usb_error_t error)
{
struct uaudio_chan *ch = usbd_xfer_softc(xfer);
struct usb_page_cache *pc;
+ uint32_t mfl;
uint32_t total;
uint32_t blockcount;
uint32_t n;
@@ -1423,12 +1537,18 @@ tr_transferred:
}
chn_intr(ch->pcm_ch);
+ /* start SYNC transfer, if any */
+ if ((ch->last_sync_time++ & 7) == 0)
+ usbd_transfer_start(ch->xfer[UAUDIO_NCHANBUFS]);
+
case USB_ST_SETUP:
- if (ch->bytes_per_frame[1] > usbd_xfer_max_framelen(xfer)) {
+ mfl = usbd_xfer_max_framelen(xfer);
+
+ if (ch->bytes_per_frame[1] > mfl) {
DPRINTF("bytes per transfer, %d, "
"exceeds maximum, %d!\n",
ch->bytes_per_frame[1],
- usbd_xfer_max_framelen(xfer));
+ mfl);
break;
}
@@ -1442,15 +1562,37 @@ tr_transferred:
/* setup frame lengths */
for (n = 0; n != blockcount; n++) {
+ uint32_t frame_len;
+
ch->sample_curr += ch->sample_rem;
if (ch->sample_curr >= ch->frames_per_second) {
ch->sample_curr -= ch->frames_per_second;
- usbd_xfer_set_frame_len(xfer, n, ch->bytes_per_frame[1]);
- total += ch->bytes_per_frame[1];
+ frame_len = ch->bytes_per_frame[1];
} else {
- usbd_xfer_set_frame_len(xfer, n, ch->bytes_per_frame[0]);
- total += ch->bytes_per_frame[0];
+ frame_len = ch->bytes_per_frame[0];
+ }
+
+ if (n == (blockcount - 1)) {
+ switch (ch->last_sync_state) {
+ case UAUDIO_SYNC_MORE:
+ DPRINTFN(6, "sending one sample more\n");
+ if ((frame_len + ch->sample_size) <= mfl)
+ frame_len += ch->sample_size;
+ ch->last_sync_state = UAUDIO_SYNC_NONE;
+ break;
+ case UAUDIO_SYNC_LESS:
+ DPRINTFN(6, "sending one sample less\n");
+ if (frame_len >= ch->sample_size)
+ frame_len -= ch->sample_size;
+ ch->last_sync_state = UAUDIO_SYNC_NONE;
+ break;
+ default:
+ break;
+ }
}
+
+ usbd_xfer_set_frame_len(xfer, n, frame_len);
+ total += frame_len;
}
DPRINTFN(6, "transfer %d bytes\n", total);
@@ -1487,6 +1629,12 @@ tr_transferred:
}
static void
+uaudio_chan_record_sync_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+ /* TODO */
+}
+
+static void
uaudio_chan_record_callback(struct usb_xfer *xfer, usb_error_t error)
{
struct uaudio_chan *ch = usbd_xfer_softc(xfer);
@@ -1697,7 +1845,7 @@ uaudio_chan_init(struct uaudio_softc *sc, struct snd_dbuf *b,
}
}
if (usbd_transfer_setup(sc->sc_udev, &iface_index, ch->xfer,
- ch->usb_cfg, UAUDIO_NCHANBUFS, ch, ch->pcm_mtx)) {
+ ch->usb_cfg, UAUDIO_NCHANBUFS + 1, ch, ch->pcm_mtx)) {
DPRINTF("could not allocate USB transfers!\n");
goto error;
}
@@ -1767,7 +1915,7 @@ uaudio_chan_free(struct uaudio_chan *ch)
free(ch->buf, M_DEVBUF);
ch->buf = NULL;
}
- usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS);
+ usbd_transfer_unsetup(ch->xfer, UAUDIO_NCHANBUFS + 1);
ch->valid = 0;
@@ -1868,12 +2016,8 @@ uaudio_chan_start(struct uaudio_chan *ch)
#if (UAUDIO_NCHANBUFS != 2)
#error "please update code"
#endif
- if (ch->xfer[0]) {
- usbd_transfer_start(ch->xfer[0]);
- }
- if (ch->xfer[1]) {
- usbd_transfer_start(ch->xfer[1]);
- }
+ usbd_transfer_start(ch->xfer[0]);
+ usbd_transfer_start(ch->xfer[1]);
return (0);
}
OpenPOWER on IntegriCloud