#include <sys/param.h>
#include <sys/types.h>
#include <sys/device.h>
#include <sys/errno.h>
#include <sys/fcntl.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/audioio.h>
#include <machine/bus.h>
#include <dev/audio_if.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdivar.h>
#include <dev/usb/usb_mem.h>
#ifdef UAUDIO_DEBUG
#define DPRINTF(...) \
do { \
if (uaudio_debug) \
printf(__VA_ARGS__); \
} while (0)
#else
#define DPRINTF(...) do {} while(0)
#endif
#define DEVNAME(sc) ((sc)->dev.dv_xname)
#define UE_ISO_USAGE 0x30
#define UE_ISO_USAGE_DATA 0x00
#define UE_ISO_USAGE_FEEDBACK 0x10
#define UE_ISO_USAGE_IMPL 0x20
#define UE_GET_ISO_USAGE(a) ((a) & UE_ISO_USAGE)
#define UAUDIO_NAMEMAX MAX_AUDIO_DEV_LEN
#define UAUDIO_V1 0x100
#define UAUDIO_V2 0x200
#define UAUDIO_AC_HEADER 0x1
#define UAUDIO_AC_INPUT 0x2
#define UAUDIO_AC_OUTPUT 0x3
#define UAUDIO_AC_MIXER 0x4
#define UAUDIO_AC_SELECTOR 0x5
#define UAUDIO_AC_FEATURE 0x6
#define UAUDIO_AC_EFFECT 0x7
#define UAUDIO_AC_PROCESSING 0x8
#define UAUDIO_AC_EXTENSION 0x9
#define UAUDIO_AC_CLKSRC 0xa
#define UAUDIO_AC_CLKSEL 0xb
#define UAUDIO_AC_CLKMULT 0xc
#define UAUDIO_AC_RATECONV 0xd
#define UAUDIO_CLKSRC_FREQCTL 0x2
#define UAUDIO_AS_GENERAL 0x1
#define UAUDIO_AS_FORMAT 0x2
#define UAUDIO_AS_EP_GENERAL 0x1
#define UAUDIO_EP_GENERAL 0x1
#define UAUDIO_V1_FMT_PCM 0x1
#define UAUDIO_V1_FMT_PCM8 0x2
#define UAUDIO_V1_FMT_FLOAT 0x3
#define UAUDIO_V1_FMT_ALAW 0x4
#define UAUDIO_V1_FMT_MULAW 0x5
#define UAUDIO_V2_FMT_PCM 0x01
#define UAUDIO_V2_FMT_PCM8 0x02
#define UAUDIO_V2_FMT_FLOAT 0x04
#define UAUDIO_V2_FMT_ALAW 0x08
#define UAUDIO_V2_FMT_MULAW 0x10
#define UAUDIO_V1_REQ_SET_CUR 0x01
#define UAUDIO_V1_REQ_SET_MIN 0x02
#define UAUDIO_V1_REQ_SET_MAX 0x03
#define UAUDIO_V1_REQ_SET_RES 0x04
#define UAUDIO_V1_REQ_GET_CUR 0x81
#define UAUDIO_V1_REQ_GET_MIN 0x82
#define UAUDIO_V1_REQ_GET_MAX 0x83
#define UAUDIO_V1_REQ_GET_RES 0x84
#define UAUDIO_V2_REQ_CUR 1
#define UAUDIO_V2_REQ_RANGES 2
#define UAUDIO_V2_REQSEL_CLKFREQ 1
#define UAUDIO_V2_REQSEL_CLKSEL 1
#define UAUDIO_EP_FREQCTL 0x01
#define UAUDIO_REQSEL_MUTE 0x01
#define UAUDIO_REQSEL_VOLUME 0x02
#define UAUDIO_REQSEL_BASS 0x03
#define UAUDIO_REQSEL_MID 0x04
#define UAUDIO_REQSEL_TREBLE 0x05
#define UAUDIO_REQSEL_EQ 0x06
#define UAUDIO_REQSEL_AGC 0x07
#define UAUDIO_REQSEL_DELAY 0x08
#define UAUDIO_REQSEL_BASSBOOST 0x09
#define UAUDIO_REQSEL_LOUDNESS 0x0a
#define UAUDIO_REQSEL_GAIN 0x0b
#define UAUDIO_REQSEL_GAINPAD 0x0c
#define UAUDIO_REQSEL_PHASEINV 0x0d
#define UAUDIO_REQSEL_RATE 0x01
#define UAUDIO_SPF_DIV 327680
#define UAUDIO_NAME_PLAY "dac"
#define UAUDIO_NAME_REC "record"
struct uaudio_blob {
unsigned char *rptr, *wptr;
};
struct uaudio_ranges {
unsigned int nval;
struct uaudio_ranges_el {
struct uaudio_ranges_el *next;
int min, max, res;
} *el;
};
struct uaudio_softc {
struct device dev;
struct usbd_device *udev;
int version;
int instnum;
struct uaudio_unit {
struct uaudio_unit *unit_next, *src_next, *dst_next;
struct uaudio_unit *src_list, *dst_list;
char name[UAUDIO_NAMEMAX];
unsigned int nch;
int type, id;
unsigned int term;
struct uaudio_unit *clock;
struct uaudio_ranges rates;
int cap_freqctl;
#define UAUDIO_CLASS_OUT 0
#define UAUDIO_CLASS_IN 1
#define UAUDIO_CLASS_COUNT 2
int mixer_class;
struct uaudio_mixent {
struct uaudio_mixent *next;
char *fname;
#define UAUDIO_MIX_SW 0
#define UAUDIO_MIX_NUM 1
#define UAUDIO_MIX_ENUM 2
int type;
int chan;
int req_sel;
struct uaudio_ranges ranges;
} *mixent_list;
} *unit_list;
struct uaudio_unit *pclock, *rclock;
unsigned int nin, nout;
struct uaudio_name {
struct uaudio_name *next;
char *templ;
unsigned int unit;
} *names;
struct uaudio_alt {
struct uaudio_alt *next;
int ifnum, altnum;
int mode;
int data_addr;
int sync_addr;
int maxpkt;
int fps;
int bps, bits, nch;
int v1_rates;
int v1_cap_freqctl;
} *alts;
struct uaudio_params {
struct uaudio_params *next;
struct uaudio_alt *palt, *ralt;
int v1_rates;
} *params_list, *params;
struct uaudio_stream {
#define UAUDIO_NXFERS_MIN 2
#define UAUDIO_NXFERS_MAX 8
struct uaudio_xfer {
struct usbd_xfer *usb_xfer;
unsigned char *buf;
uint16_t *sizes;
unsigned int size;
unsigned int nframes;
} data_xfers[UAUDIO_NXFERS_MAX], sync_xfers[UAUDIO_NXFERS_MAX];
unsigned int nxfers;
unsigned int spf_remain;
unsigned int spf;
unsigned int spf_min, spf_max;
unsigned int maxpkt;
unsigned int nframes_max;
unsigned int nframes_mask;
unsigned int data_nextxfer, sync_nextxfer;
struct usbd_pipe *data_pipe;
struct usbd_pipe *sync_pipe;
void (*intr)(void *);
void *arg;
unsigned char *ring_start, *ring_end;
unsigned char *ring_pos;
int ring_blksz;
int ring_offs;
int safe_blksz;
int ring_icnt;
unsigned int ubuf_xfer;
unsigned int ubuf_pos;
} pstream, rstream;
int ctl_ifnum;
int mode;
int trigger_mode;
unsigned int rate;
unsigned int ufps;
unsigned int sync_pktsz;
unsigned int host_nframes;
int diff_nsamp;
int diff_nframes;
unsigned int adjspf_age;
size_t copy_todo;
};
int uaudio_match(struct device *, void *, void *);
void uaudio_attach(struct device *, struct device *, void *);
int uaudio_detach(struct device *, int);
int uaudio_open(void *, int);
void uaudio_close(void *);
int uaudio_set_params(void *, int, int, struct audio_params *,
struct audio_params *);
unsigned int uaudio_set_blksz(void *, int,
struct audio_params *, struct audio_params *, unsigned int);
int uaudio_trigger_output(void *, void *, void *, int,
void (*)(void *), void *, struct audio_params *);
int uaudio_trigger_input(void *, void *, void *, int,
void (*)(void *), void *, struct audio_params *);
void uaudio_copy_output(void *, size_t);
void uaudio_underrun(void *);
int uaudio_halt_output(void *);
int uaudio_halt_input(void *);
int uaudio_query_devinfo(void *, struct mixer_devinfo *);
int uaudio_get_port(void *, struct mixer_ctrl *);
int uaudio_set_port(void *, struct mixer_ctrl *);
size_t uaudio_display_name(void *, char *, size_t);
int uaudio_process_unit(struct uaudio_softc *,
struct uaudio_unit *, int,
struct uaudio_blob,
struct uaudio_unit **);
void uaudio_pdata_intr(struct usbd_xfer *, void *, usbd_status);
void uaudio_rdata_intr(struct usbd_xfer *, void *, usbd_status);
void uaudio_psync_intr(struct usbd_xfer *, void *, usbd_status);
#ifdef UAUDIO_DEBUG
char *uaudio_isoname(int isotype);
char *uaudio_modename(int mode);
char *uaudio_usagename(int usage);
void uaudio_rates_print(int rates);
void uaudio_ranges_print(struct uaudio_ranges *r);
void uaudio_print_unit(struct uaudio_softc *sc, struct uaudio_unit *u);
void uaudio_mixer_print(struct uaudio_softc *sc);
void uaudio_conf_print(struct uaudio_softc *sc);
int uaudio_debug = 1;
#endif
struct cfdriver uaudio_cd = {
NULL, "uaudio", DV_DULL
};
const struct cfattach uaudio_ca = {
sizeof(struct uaudio_softc), uaudio_match, uaudio_attach, uaudio_detach
};
const struct audio_hw_if uaudio_hw_if = {
.open = uaudio_open,
.close = uaudio_close,
.set_params = uaudio_set_params,
.halt_output = uaudio_halt_output,
.halt_input = uaudio_halt_input,
.set_port = uaudio_set_port,
.get_port = uaudio_get_port,
.query_devinfo = uaudio_query_devinfo,
.trigger_output = uaudio_trigger_output,
.trigger_input = uaudio_trigger_input,
.copy_output = uaudio_copy_output,
.underrun = uaudio_underrun,
.set_blksz = uaudio_set_blksz,
.display_name = uaudio_display_name,
};
const int uaudio_rates[] = {
8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000,
64000, 88200, 96000, 128000, 176400, 192000
};
int
uaudio_sign_expand(unsigned int val, int opsize)
{
unsigned int s;
s = 1 << (8 * opsize - 1);
return (val ^ s) - s;
}
int
uaudio_req(struct uaudio_softc *sc,
unsigned int type,
unsigned int req,
unsigned int sel,
unsigned int chan,
unsigned int ifnum,
unsigned int id,
unsigned char *buf,
size_t size)
{
struct usb_device_request r;
int err;
r.bmRequestType = type;
r.bRequest = req;
USETW(r.wValue, sel << 8 | chan);
USETW(r.wIndex, id << 8 | ifnum);
USETW(r.wLength, size);
DPRINTF("%s: type = 0x%x, req = 0x%x, val = 0x%x, "
"index = 0x%x, size = %d\n", __func__,
type, req, UGETW(r.wValue), UGETW(r.wIndex), UGETW(r.wLength));
err = usbd_do_request(sc->udev, &r, buf);
if (err) {
DPRINTF("%s: failed: %s\n", __func__, usbd_errstr(err));
return 0;
}
return 1;
}
int
uaudio_getnum(struct uaudio_blob *p, unsigned int size, unsigned int *ret)
{
unsigned int i, num = 0;
if (p->wptr - p->rptr < size) {
DPRINTF("%s: %d: too small\n", __func__, size);
return 0;
}
for (i = 0; i < size; i++)
num |= *p->rptr++ << (8 * i);
if (ret)
*ret = num;
return 1;
}
int
uaudio_getdesc(struct uaudio_blob *p, struct uaudio_blob *ret)
{
unsigned int size;
if (!uaudio_getnum(p, 1, &size))
return 0;
if (size-- == 0) {
DPRINTF("%s: zero sized desc\n", __func__);
return 0;
}
if (p->wptr - p->rptr < size) {
DPRINTF("%s: too small\n", __func__);
return 0;
}
ret->rptr = p->rptr;
ret->wptr = p->rptr + size;
p->rptr += size;
return 1;
}
struct uaudio_unit *
uaudio_unit_byid(struct uaudio_softc *sc, unsigned int id)
{
struct uaudio_unit *u;
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
if (u->id == id)
break;
}
return u;
}
char *
uaudio_tname(struct uaudio_softc *sc, unsigned int type, int isout)
{
unsigned int hi, lo;
char *name;
hi = type >> 8;
lo = type & 0xff;
if (hi == 1)
return isout ? UAUDIO_NAME_REC : UAUDIO_NAME_PLAY;
if (isout) {
if (sc->nout == 1)
return "output";
} else {
if (sc->nin == 1)
return "input";
}
switch (hi) {
case 2:
name = isout ? "mic-out" : "mic";
break;
case 3:
switch (lo) {
case 0x02:
name = isout ? "hp" : "hp-in";
break;
default:
name = isout ? "spkr" : "spkr-in";
break;
}
break;
case 4:
name = isout ? "spkr" : "mic";
break;
case 5:
name = isout ? "phone-in" : "phone-out";
break;
case 6:
switch (lo) {
case 0x02:
case 0x05:
case 0x06:
case 0x07:
case 0x09:
case 0x0a:
name = isout ? "dig-out" : "dig-in";
break;
default:
name = isout ? "line-out" : "line-in";
break;
}
break;
case 7:
name = isout ? "int-out" : "int-in";
break;
default:
name = isout ? "unk-out" : "unk-in";
}
return name;
}
char *
uaudio_clkname(unsigned int attr)
{
static char *names[] = {"ext", "fixed", "var", "prog"};
return names[attr & 3];
}
void
uaudio_mkname(struct uaudio_softc *sc, char *templ, char *res)
{
struct uaudio_name *n;
char *sep;
sep = strchr(templ, '_') != NULL ? "_" : "";
n = sc->names;
while (1) {
if (n == NULL) {
n = malloc(sizeof(struct uaudio_name),
M_USBDEV, M_WAITOK);
n->templ = templ;
n->unit = 0;
n->next = sc->names;
sc->names = n;
}
if (strcmp(n->templ, templ) == 0)
break;
n = n->next;
}
if (n->unit == 0)
snprintf(res, UAUDIO_NAMEMAX, "%s", templ);
else
snprintf(res, UAUDIO_NAMEMAX, "%s%s%u", templ, sep, n->unit);
n->unit++;
}
unsigned int
uaudio_feature_fixup(struct uaudio_softc *sc, unsigned int ctl)
{
int i;
unsigned int bits, n;
switch (sc->version) {
case UAUDIO_V1:
n = 0;
for (i = 0; i < 16; i++) {
bits = (ctl >> i) & 1;
if (bits)
bits |= 2;
n |= bits << (2 * i);
}
return n;
case UAUDIO_V2:
break;
}
return ctl;
}
void
uaudio_ranges_init(struct uaudio_ranges *r)
{
r->el = NULL;
r->nval = 0;
}
void
uaudio_ranges_add(struct uaudio_ranges *r, int min, int max, int res)
{
struct uaudio_ranges_el *e, **pe;
if (min > max) {
DPRINTF("%s: [%d:%d]/%d: bad range\n", __func__,
min, max, res);
return;
}
for (pe = &r->el; (e = *pe) != NULL; pe = &e->next) {
if (min <= e->max && max >= e->min) {
DPRINTF("%s: overlapping ranges\n", __func__);
return;
}
if (min < e->max)
break;
}
r->nval += max - min + 1;
e = malloc(sizeof(struct uaudio_ranges_el), M_USBDEV, M_WAITOK);
e->min = min;
e->max = max;
e->res = res;
e->next = *pe;
*pe = e;
}
void
uaudio_ranges_clear(struct uaudio_ranges *r)
{
struct uaudio_ranges_el *e;
while ((e = r->el) != NULL) {
r->el = e->next;
free(e, M_USBDEV, sizeof(struct uaudio_ranges_el));
}
r->nval = 0;
}
int
uaudio_ranges_decode(struct uaudio_ranges *r, int val)
{
struct uaudio_ranges_el *e;
int diff, pos;
pos = 0;
for (e = r->el; e != NULL; e = e->next) {
if (val >= e->min && val <= e->max) {
pos += val - e->min;
return (r->nval == 1) ? 0 :
(pos * 255 + (r->nval - 1) / 2) / (r->nval - 1);
}
diff = e->max - e->min + 1;
pos += diff;
}
return 0;
}
unsigned int
uaudio_ranges_encode(struct uaudio_ranges *r, int val)
{
struct uaudio_ranges_el *e;
int diff, pos;
pos = (val * (r->nval - 1) + 127) / 255;
for (e = r->el; e != NULL; e = e->next) {
diff = e->max - e->min + 1;
if (pos < diff)
return e->min + pos;
pos -= diff;
}
return 0;
}
int
uaudio_ranges_getrates(struct uaudio_ranges *r,
unsigned int mult, unsigned int div)
{
struct uaudio_ranges_el *e;
int rates, i, v;
rates = 0;
for (e = r->el; e != NULL; e = e->next) {
for (i = 0; i < nitems(uaudio_rates); i++) {
v = (unsigned long long)uaudio_rates[i] * mult / div;
if (v < e->min || v > e->max)
continue;
if (e->res == 0 || v - e->min % e->res == 0)
rates |= 1 << i;
}
}
return rates;
}
int
uaudio_rates_indexof(int mask, int rate)
{
int i, diff, best_index, best_diff;
best_index = -1;
best_diff = INT_MAX;
for (i = 0; i < nitems(uaudio_rates); i++) {
if ((mask & (1 << i)) == 0)
continue;
diff = uaudio_rates[i] - rate;
if (diff < 0)
diff = -diff;
if (diff < best_diff) {
best_index = i;
best_diff = diff;
}
}
return best_index;
}
int
uaudio_req_ranges(struct uaudio_softc *sc,
unsigned int opsize,
unsigned int sel,
unsigned int chan,
unsigned int ifnum,
unsigned int id,
struct uaudio_ranges *r)
{
unsigned char req_buf[16], *req = NULL;
size_t req_size;
struct uaudio_blob p;
unsigned int count, min, max, res;
int i;
switch (sc->version) {
case UAUDIO_V1:
count = 1;
req = req_buf;
p.rptr = p.wptr = req;
if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE,
UAUDIO_V1_REQ_GET_MIN, sel, chan,
ifnum, id, p.wptr, opsize))
return 0;
p.wptr += opsize;
if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE,
UAUDIO_V1_REQ_GET_MAX, sel, chan,
ifnum, id, p.wptr, opsize))
return 0;
p.wptr += opsize;
if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE,
UAUDIO_V1_REQ_GET_RES, sel, chan,
ifnum, id, p.wptr, opsize))
return 0;
p.wptr += opsize;
break;
case UAUDIO_V2:
if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE,
UAUDIO_V2_REQ_RANGES, sel, chan,
ifnum, id, req_buf, 2))
return 0;
count = req_buf[0] | req_buf[1] << 8;
req_size = 2 + 3 * opsize * count;
if (sizeof(req_buf) >= req_size)
req = req_buf;
else
req = malloc(req_size, M_USBDEV, M_WAITOK);
p.rptr = p.wptr = req;
if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE,
UAUDIO_V2_REQ_RANGES, sel, chan,
ifnum, id, p.wptr, req_size))
return 0;
p.wptr += req_size;
p.rptr += 2;
break;
}
for (i = 0; i < count; i++) {
if (!uaudio_getnum(&p, opsize, &min))
return 0;
if (!uaudio_getnum(&p, opsize, &max))
return 0;
if (!uaudio_getnum(&p, opsize, &res))
return 0;
uaudio_ranges_add(r,
uaudio_sign_expand(min, opsize),
uaudio_sign_expand(max, opsize),
uaudio_sign_expand(res, opsize));
}
if (req != req_buf)
free(req, M_USBDEV, req_size);
return 1;
}
int
uaudio_alt_getrates(struct uaudio_softc *sc, struct uaudio_alt *p)
{
struct uaudio_unit *u;
unsigned int mult = 1, div = 1;
switch (sc->version) {
case UAUDIO_V1:
return p->v1_rates;
case UAUDIO_V2:
u = p->mode == AUMODE_PLAY ? sc->pclock : sc->rclock;
while (1) {
switch (u->type) {
case UAUDIO_AC_CLKSRC:
return uaudio_ranges_getrates(&u->rates,
mult, div);
case UAUDIO_AC_CLKSEL:
u = u->clock;
break;
case UAUDIO_AC_CLKMULT:
case UAUDIO_AC_RATECONV:
u = u->src_list;
break;
default:
DPRINTF("%s: no clock\n", __func__);
return 0;
}
}
}
return 0;
}
struct uaudio_unit *
uaudio_clock(struct uaudio_unit *u)
{
while (1) {
if (u == NULL) {
DPRINTF("%s: NULL clock pointer\n", __func__);
return NULL;
}
switch (u->type) {
case UAUDIO_AC_CLKSRC:
return u;
case UAUDIO_AC_CLKSEL:
u = u->clock;
break;
case UAUDIO_AC_CLKMULT:
case UAUDIO_AC_RATECONV:
u = u->src_list;
break;
default:
DPRINTF("%s: no clock\n", __func__);
return NULL;
}
}
}
int
uaudio_getrates(struct uaudio_softc *sc, int mode, struct uaudio_params *p)
{
int rates;
switch (sc->version) {
case UAUDIO_V1:
return p->v1_rates;
case UAUDIO_V2:
rates = ~0;
if (p->palt != NULL && (mode & AUMODE_PLAY))
rates &= uaudio_alt_getrates(sc, p->palt);
if (p->ralt != NULL && (mode & AUMODE_RECORD))
rates &= uaudio_alt_getrates(sc, p->ralt);
return rates;
}
return 0;
}
void
uaudio_feature_addent(struct uaudio_softc *sc,
struct uaudio_unit *u, int uac_type, int chan)
{
static struct {
char *name;
int mix_type;
int req_sel;
} features[] = {
{"mute", UAUDIO_MIX_SW, UAUDIO_REQSEL_MUTE},
{"level", UAUDIO_MIX_NUM, UAUDIO_REQSEL_VOLUME},
{"bass", UAUDIO_MIX_NUM, UAUDIO_REQSEL_BASS},
{"mid", UAUDIO_MIX_NUM, UAUDIO_REQSEL_MID},
{"treble", UAUDIO_MIX_NUM, UAUDIO_REQSEL_TREBLE},
{"eq", UAUDIO_MIX_NUM, UAUDIO_REQSEL_EQ},
{"agc", UAUDIO_MIX_SW, UAUDIO_REQSEL_AGC},
{NULL, -1, -1},
{"bassboost", UAUDIO_MIX_SW, UAUDIO_REQSEL_BASSBOOST},
{"loud", UAUDIO_MIX_SW, UAUDIO_REQSEL_LOUDNESS},
{"gain", UAUDIO_MIX_NUM, UAUDIO_REQSEL_GAIN},
{"gainpad", UAUDIO_MIX_SW, UAUDIO_REQSEL_GAINPAD},
{"phase", UAUDIO_MIX_SW, UAUDIO_REQSEL_PHASEINV},
{NULL, -1, -1},
{NULL, -1, -1}
};
struct uaudio_mixent *m, *i, **pi;
int cmp;
if (uac_type >= sizeof(features) / sizeof(features[0])) {
printf("%s: skipped unknown feature\n", DEVNAME(sc));
return;
}
m = malloc(sizeof(struct uaudio_mixent), M_USBDEV, M_WAITOK);
m->chan = chan;
m->fname = features[uac_type].name;
m->type = features[uac_type].mix_type;
m->req_sel = features[uac_type].req_sel;
uaudio_ranges_init(&m->ranges);
if (m->type == UAUDIO_MIX_NUM) {
if (!uaudio_req_ranges(sc, 2,
m->req_sel, chan < 0 ? 0 : chan + 1,
sc->ctl_ifnum, u->id,
&m->ranges)) {
printf("%s: failed to get ranges for %s control\n",
DEVNAME(sc), m->fname);
free(m, M_USBDEV, sizeof(struct uaudio_mixent));
return;
}
if (m->ranges.el == NULL) {
printf("%s: skipped %s control with empty range\n",
DEVNAME(sc), m->fname);
free(m, M_USBDEV, sizeof(struct uaudio_mixent));
return;
}
#ifdef UAUDIO_DEBUG
if (uaudio_debug)
uaudio_ranges_print(&m->ranges);
#endif
}
for (pi = &u->mixent_list; (i = *pi) != NULL; pi = &i->next) {
cmp = strcmp(i->fname, m->fname);
if (cmp == 0)
cmp = i->chan - m->chan;
if (cmp == 0) {
DPRINTF("%02u: %s.%s: duplicate feature for chan %d\n",
u->id, u->name, m->fname, m->chan);
free(m, M_USBDEV, sizeof(struct uaudio_mixent));
return;
}
if (cmp > 0)
break;
}
m->next = *pi;
*pi = m;
DPRINTF("\t%s[%d]\n", m->fname, m->chan);
}
int
uaudio_clock_equiv(struct uaudio_softc *sc,
struct uaudio_unit *u, struct uaudio_unit *v)
{
if (u == NULL || v == NULL)
return 1;
u = uaudio_clock(u);
if (u == NULL)
return 0;
v = uaudio_clock(v);
if (v == NULL)
return 0;
if (u->term != v->term) {
printf("%s: clock attributes differ\n", DEVNAME(sc));
return 0;
}
if (u != v)
printf("%s: warning: assuming common clock\n", DEVNAME(sc));
return 1;
}
int
uaudio_process_srcs(struct uaudio_softc *sc,
struct uaudio_unit *u, struct uaudio_blob units,
struct uaudio_blob *p)
{
struct uaudio_unit *s, **ps;
unsigned int i, npin, sid;
if (!uaudio_getnum(p, 1, &npin))
return 0;
ps = &u->src_list;
for (i = 0; i < npin; i++) {
if (!uaudio_getnum(p, 1, &sid))
return 0;
if (!uaudio_process_unit(sc, u, sid, units, &s))
return 0;
s->src_next = NULL;
*ps = s;
ps = &s->src_next;
}
return 1;
}
int
uaudio_process_nch(struct uaudio_softc *sc,
struct uaudio_unit *u, struct uaudio_blob *p)
{
if (!uaudio_getnum(p, 1, &u->nch))
return 0;
switch (sc->version) {
case UAUDIO_V1:
if (!uaudio_getnum(p, 2, NULL))
return 0;
break;
case UAUDIO_V2:
if (!uaudio_getnum(p, 4, NULL))
return 0;
break;
}
if (!uaudio_getnum(p, 1, NULL))
return 0;
return 1;
}
int
uaudio_unit_getdesc(struct uaudio_softc *sc, int id,
struct uaudio_blob units,
struct uaudio_blob *p,
unsigned int *rtype)
{
unsigned int i, type, subtype;
while (1) {
if (units.rptr == units.wptr) {
DPRINTF("%s: %02u: not found\n", __func__, id);
return 0;
}
if (!uaudio_getdesc(&units, p))
return 0;
if (!uaudio_getnum(p, 1, &type))
return 0;
if (!uaudio_getnum(p, 1, &subtype))
return 0;
if (!uaudio_getnum(p, 1, &i))
return 0;
if (i == id)
break;
}
*rtype = subtype;
return 1;
}
int
uaudio_process_unit(struct uaudio_softc *sc,
struct uaudio_unit *dest, int id,
struct uaudio_blob units,
struct uaudio_unit **rchild)
{
struct uaudio_blob p;
struct uaudio_unit *u, *s;
unsigned int i, j, size, ctl, type, subtype, assoc, clk;
#ifdef UAUDIO_DEBUG
unsigned int bit;
#endif
if (!uaudio_unit_getdesc(sc, id, units, &p, &subtype))
return 0;
u = uaudio_unit_byid(sc, id);
if (u == NULL) {
u = malloc(sizeof(struct uaudio_unit), M_USBDEV, M_WAITOK);
u->id = id;
u->type = subtype;
u->term = 0;
u->src_list = NULL;
u->dst_list = NULL;
u->clock = NULL;
u->mixent_list = NULL;
u->nch = 0;
u->name[0] = 0;
u->cap_freqctl = 0;
uaudio_ranges_init(&u->rates);
u->unit_next = sc->unit_list;
sc->unit_list = u;
} else {
switch (u->type) {
case UAUDIO_AC_CLKSRC:
case UAUDIO_AC_CLKSEL:
case UAUDIO_AC_CLKMULT:
case UAUDIO_AC_RATECONV:
*rchild = u;
return 1;
}
}
if (dest) {
dest->dst_next = u->dst_list;
u->dst_list = dest;
if (dest->dst_next != NULL) {
*rchild = u;
return 1;
}
}
switch (u->type) {
case UAUDIO_AC_INPUT:
if (!uaudio_getnum(&p, 2, &u->term))
return 0;
if (!uaudio_getnum(&p, 1, &assoc))
return 0;
if (u->term >> 8 != 1)
sc->nin++;
switch (sc->version) {
case UAUDIO_V1:
break;
case UAUDIO_V2:
if (!uaudio_getnum(&p, 1, &clk))
return 0;
if (!uaudio_process_unit(sc, NULL,
clk, units, &u->clock))
return 0;
break;
}
if (!uaudio_getnum(&p, 1, &u->nch))
return 0;
DPRINTF("%02u: "
"in, nch = %d, term = 0x%x, assoc = %d\n",
u->id, u->nch, u->term, assoc);
break;
case UAUDIO_AC_OUTPUT:
if (!uaudio_getnum(&p, 2, &u->term))
return 0;
if (!uaudio_getnum(&p, 1, &assoc))
return 0;
if (!uaudio_getnum(&p, 1, &id))
return 0;
if (!uaudio_process_unit(sc, u, id, units, &s))
return 0;
if (u->term >> 8 != 1)
sc->nout++;
switch (sc->version) {
case UAUDIO_V1:
break;
case UAUDIO_V2:
if (!uaudio_getnum(&p, 1, &clk))
return 0;
if (!uaudio_process_unit(sc, NULL,
clk, units, &u->clock))
return 0;
break;
}
u->src_list = s;
s->src_next = NULL;
u->nch = s->nch;
DPRINTF("%02u: "
"out, id = %d, nch = %d, term = 0x%x, assoc = %d\n",
u->id, id, u->nch, u->term, assoc);
break;
case UAUDIO_AC_MIXER:
if (!uaudio_process_srcs(sc, u, units, &p))
return 0;
if (!uaudio_process_nch(sc, u, &p))
return 0;
DPRINTF("%02u: mixer, nch = %u:\n", u->id, u->nch);
#ifdef UAUDIO_DEBUG
bit = 0;
for (s = u->src_list; s != NULL; s = s->src_next) {
for (i = 0; i < s->nch; i++) {
for (j = 0; j < u->nch; j++) {
if ((bit++ & 7) == 0) {
if (!uaudio_getnum(&p, 1, &ctl))
return 0;
}
if (ctl & 0x80)
DPRINTF("\t%02u[%d] -> [%d]\n",
s->id, i, j);
ctl <<= 1;
}
}
}
#endif
break;
case UAUDIO_AC_SELECTOR:
if (!uaudio_process_srcs(sc, u, units, &p))
return 0;
if (u->src_list == NULL) {
printf("%s: selector %02u has no sources\n",
DEVNAME(sc), u->id);
return 0;
}
u->nch = u->src_list->nch;
DPRINTF("%02u: selector, nch = %u\n", u->id, u->nch);
break;
case UAUDIO_AC_FEATURE:
if (!uaudio_getnum(&p, 1, &id))
return 0;
if (!uaudio_process_unit(sc, u, id, units, &s))
return 0;
s->src_next = u->src_list;
u->src_list = s;
u->nch = s->nch;
switch (sc->version) {
case UAUDIO_V1:
if (!uaudio_getnum(&p, 1, &size))
return 0;
break;
case UAUDIO_V2:
size = 4;
break;
}
DPRINTF("%02d: feature id = %d, nch = %d, size = %d\n",
u->id, id, u->nch, size);
if (!uaudio_getnum(&p, size, &ctl))
return 0;
ctl = uaudio_feature_fixup(sc, ctl);
for (i = 0; i < 16; i++) {
if ((ctl & 3) == 3)
uaudio_feature_addent(sc, u, i, -1);
ctl >>= 2;
}
if (p.wptr - p.rptr == 1)
break;
for (j = 0; j < u->nch; j++) {
if (!uaudio_getnum(&p, size, &ctl))
return 0;
ctl = uaudio_feature_fixup(sc, ctl);
for (i = 0; i < 16; i++) {
if ((ctl & 3) == 3)
uaudio_feature_addent(sc, u, i, j);
ctl >>= 2;
}
}
break;
case UAUDIO_AC_EFFECT:
if (!uaudio_getnum(&p, 2, &type))
return 0;
if (!uaudio_getnum(&p, 1, &id))
return 0;
if (!uaudio_process_unit(sc, u, id, units, &s))
return 0;
s->src_next = u->src_list;
u->src_list = s;
u->nch = s->nch;
DPRINTF("%02d: effect, type = %u, id = %d, nch = %d\n",
u->id, type, id, u->nch);
break;
case UAUDIO_AC_PROCESSING:
case UAUDIO_AC_EXTENSION:
if (!uaudio_getnum(&p, 2, &type))
return 0;
if (!uaudio_process_srcs(sc, u, units, &p))
return 0;
if (!uaudio_process_nch(sc, u, &p))
return 0;
DPRINTF("%02u: proc/ext, type = 0x%x, nch = %u\n",
u->id, type, u->nch);
for (s = u->src_list; s != NULL; s = s->src_next) {
DPRINTF("%u:\tpin %u:\n", u->id, s->id);
}
break;
case UAUDIO_AC_CLKSRC:
if (!uaudio_getnum(&p, 1, &u->term))
return 0;
if (!uaudio_getnum(&p, 1, &ctl))
return 0;
DPRINTF("%02u: clock source, attr = 0x%x, ctl = 0x%x\n",
u->id, u->term, ctl);
u->cap_freqctl = !!(ctl & UAUDIO_CLKSRC_FREQCTL);
break;
case UAUDIO_AC_CLKSEL:
DPRINTF("%02u: clock sel\n", u->id);
if (!uaudio_process_srcs(sc, u, units, &p))
return 0;
if (u->src_list == NULL) {
printf("%s: clock selector %02u with no srcs\n",
DEVNAME(sc), u->id);
return 0;
}
break;
case UAUDIO_AC_CLKMULT:
DPRINTF("%02u: clock mult\n", u->id);
printf("%s: clock multiplier not supported\n", DEVNAME(sc));
break;
case UAUDIO_AC_RATECONV:
DPRINTF("%02u: rate conv\n", u->id);
printf("%s: rate converter not supported\n", DEVNAME(sc));
break;
}
if (rchild)
*rchild = u;
return 1;
}
int
uaudio_setname_dsts(struct uaudio_softc *sc, struct uaudio_unit *u, char *name)
{
struct uaudio_unit *d = u;
while (d != NULL) {
if (d->dst_list == NULL || d->dst_list->dst_next != NULL)
break;
d = d->dst_list;
if (d->src_list == NULL || d->src_list->src_next != NULL)
break;
if (d->name[0] != '\0') {
if (name != NULL && strcmp(name, d->name) != 0)
break;
strlcpy(u->name, d->name, UAUDIO_NAMEMAX);
return 1;
}
}
return 0;
}
int
uaudio_setname_srcs(struct uaudio_softc *sc, struct uaudio_unit *u, char *name)
{
struct uaudio_unit *s = u;
while (s != NULL) {
if (s->src_list == NULL || s->src_list->src_next != NULL)
break;
s = s->src_list;
if (s->dst_list == NULL || s->dst_list->dst_next != NULL)
break;
if (s->name[0] != '\0') {
if (name != NULL && strcmp(name, s->name) != 0)
break;
strlcpy(u->name, s->name, UAUDIO_NAMEMAX);
return 1;
}
}
return 0;
}
void
uaudio_setname_middle(struct uaudio_softc *sc, struct uaudio_unit *u)
{
struct uaudio_unit *s, *d;
char name[UAUDIO_NAMEMAX];
s = u->src_list;
while (1) {
if (s == NULL) {
DPRINTF("%s: %02u: has no srcs\n",
__func__, u->id);
return;
}
if (s->name[0] != '\0')
break;
s = s->src_list;
}
d = u->dst_list;
while (1) {
if (d == NULL) {
DPRINTF("%s: %02u: has no dests\n",
__func__, u->id);
return;
}
if (d->name[0] != '\0')
break;
d = d->dst_list;
}
snprintf(name, UAUDIO_NAMEMAX, "%s_%s", d->name, s->name);
uaudio_mkname(sc, name, u->name);
}
#ifdef UAUDIO_DEBUG
char *
uaudio_isoname(int isotype)
{
switch (isotype) {
case UE_ISO_ASYNC:
return "async";
case UE_ISO_ADAPT:
return "adapt";
case UE_ISO_SYNC:
return "sync";
default:
return "unk";
}
}
char *
uaudio_modename(int mode)
{
switch (mode) {
case 0:
return "none";
case AUMODE_PLAY:
return "play";
case AUMODE_RECORD:
return "rec";
case AUMODE_PLAY | AUMODE_RECORD:
return "duplex";
default:
return "unk";
}
}
char *
uaudio_usagename(int usage)
{
switch (usage) {
case UE_ISO_USAGE_DATA:
return "data";
case UE_ISO_USAGE_FEEDBACK:
return "feed";
case UE_ISO_USAGE_IMPL:
return "impl";
default:
return "unk";
}
}
void
uaudio_rates_print(int rates)
{
unsigned int i;
for (i = 0; i < nitems(uaudio_rates); i++) {
if (rates & (1 << i))
printf(" %d", uaudio_rates[i]);
}
printf("\n");
}
void
uaudio_ranges_print(struct uaudio_ranges *r)
{
struct uaudio_ranges_el *e;
int more = 0;
for (e = r->el; e != NULL; e = e->next) {
if (more)
printf(", ");
if (e->min == e->max)
printf("%d", e->min);
else
printf("[%d:%d]/%d", e->min, e->max, e->res);
more = 1;
}
printf(" (%d vals)\n", r->nval);
}
void
uaudio_print_unit(struct uaudio_softc *sc, struct uaudio_unit *u)
{
struct uaudio_unit *s;
switch (u->type) {
case UAUDIO_AC_INPUT:
printf("%02u: input <%s>, dest = %02u <%s>\n",
u->id, u->name, u->dst_list->id, u->dst_list->name);
break;
case UAUDIO_AC_OUTPUT:
printf("%02u: output <%s>, source = %02u <%s>\n",
u->id, u->name, u->src_list->id, u->src_list->name);
break;
case UAUDIO_AC_MIXER:
printf("%02u: mixer <%s>:\n", u->id, u->name);
for (s = u->src_list; s != NULL; s = s->src_next)
printf("%02u:\tsource %u <%s>:\n",
u->id, s->id, s->name);
break;
case UAUDIO_AC_SELECTOR:
printf("%02u: selector <%s>:\n", u->id, u->name);
for (s = u->src_list; s != NULL; s = s->src_next)
printf("%02u:\tsource %u <%s>:\n",
u->id, s->id, s->name);
break;
case UAUDIO_AC_FEATURE:
printf("%02u: feature <%s>, "
"src = %02u <%s>, dst = %02u <%s>, cls = %d\n",
u->id, u->name,
u->src_list->id, u->src_list->name,
u->dst_list->id, u->dst_list->name, u->mixer_class);
break;
case UAUDIO_AC_EFFECT:
printf("%02u: effect <%s>, "
"src = %02u <%s>, dst = %02u <%s>\n",
u->id, u->name,
u->src_list->id, u->src_list->name,
u->dst_list->id, u->dst_list->name);
break;
case UAUDIO_AC_PROCESSING:
case UAUDIO_AC_EXTENSION:
printf("%02u: proc/ext <%s>:\n", u->id, u->name);
for (s = u->src_list; s != NULL; s = s->src_next)
printf("%02u:\tsource %u <%s>:\n",
u->id, s->id, s->name);
break;
case UAUDIO_AC_CLKSRC:
printf("%02u: clock source <%s>\n", u->id, u->name);
break;
case UAUDIO_AC_CLKSEL:
printf("%02u: clock sel <%s>\n", u->id, u->name);
break;
case UAUDIO_AC_CLKMULT:
printf("%02u: clock mult\n", u->id);
break;
case UAUDIO_AC_RATECONV:
printf("%02u: rate conv\n", u->id);
break;
}
}
void
uaudio_mixer_print(struct uaudio_softc *sc)
{
struct uaudio_mixent *m;
struct uaudio_unit *u;
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
for (m = u->mixent_list; m != NULL; m = m->next) {
printf("%02u:\t%s.%s",
u->id, u->name, m->fname);
if (m->chan >= 0)
printf("[%u]", m->chan);
printf("\n");
}
}
}
void
uaudio_conf_print(struct uaudio_softc *sc)
{
struct uaudio_alt *a;
struct uaudio_params *p;
struct mixer_devinfo mi;
struct mixer_ctrl ctl;
int i, rates;
mi.index = 0;
while (1) {
if (uaudio_query_devinfo(sc, &mi) != 0)
break;
if (mi.type != AUDIO_MIXER_CLASS) {
ctl.dev = mi.index;
if (uaudio_get_port(sc, &ctl) != 0) {
printf("%02u: failed to get port\n", mi.index);
memset(&ctl.un, 0, sizeof(ctl.un));
}
}
printf("%02u: <%s>, next = %d, prev = %d, class = %d",
mi.index, mi.label.name, mi.next, mi.prev, mi.mixer_class);
switch (mi.type) {
case AUDIO_MIXER_CLASS:
break;
case AUDIO_MIXER_VALUE:
printf(", nch = %d, delta = %d",
mi.un.v.num_channels, mi.un.v.delta);
printf(", val =");
for (i = 0; i < mi.un.v.num_channels; i++)
printf(" %d", ctl.un.value.level[i]);
break;
case AUDIO_MIXER_ENUM:
printf(", members:");
for (i = 0; i != mi.un.e.num_mem; i++) {
printf(" %s(=%d)",
mi.un.e.member[i].label.name,
mi.un.e.member[i].ord);
}
printf(", val = %d", ctl.un.ord);
break;
}
printf("\n");
mi.index++;
}
printf("%d controls\n", mi.index);
printf("alts:\n");
for (a = sc->alts; a != NULL; a = a->next) {
rates = uaudio_alt_getrates(sc, a);
printf("mode = %s, ifnum = %d, altnum = %d, "
"addr = 0x%x, maxpkt = %d, sync = 0x%x, "
"nch = %d, fmt = s%dle%d, rates:",
uaudio_modename(a->mode),
a->ifnum, a->altnum,
a->data_addr, a->maxpkt,
a->sync_addr,
a->nch, a->bits, a->bps);
uaudio_rates_print(rates);
}
printf("parameters:\n");
for (p = sc->params_list; p != NULL; p = p->next) {
switch (sc->version) {
case UAUDIO_V1:
rates = p->v1_rates;
break;
case UAUDIO_V2:
rates = uaudio_getrates(sc, AUMODE_PLAY | AUMODE_RECORD, p);
break;
}
printf("pchan = %d, s%dle%d, rchan = %d, s%dle%d, rates:",
p->palt ? p->palt->nch : 0,
p->palt ? p->palt->bits : 0,
p->palt ? p->palt->bps : 0,
p->ralt ? p->ralt->nch : 0,
p->ralt ? p->ralt->bits : 0,
p->ralt ? p->ralt->bps : 0);
uaudio_rates_print(rates);
}
}
#endif
int
uaudio_mixer_nchan(struct uaudio_mixent *m, struct uaudio_mixent **rnext)
{
char *name;
int i;
i = 0;
name = m->fname;
while (m != NULL && strcmp(name, m->fname) == 0) {
m = m->next;
i++;
}
if (rnext)
*rnext = m;
return i;
}
void
uaudio_mixer_skip(struct uaudio_mixent **pm)
{
struct uaudio_mixent *m = *pm;
if (m != NULL &&
m->chan == -1 &&
m->next != NULL &&
strcmp(m->fname, m->next->fname) == 0)
m = m->next;
*pm = m;
}
int
uaudio_mixer_byindex(struct uaudio_softc *sc, int index,
struct uaudio_unit **ru, struct uaudio_mixent **rm)
{
struct uaudio_unit *u;
struct uaudio_mixent *m;
char *name;
int i;
i = UAUDIO_CLASS_COUNT;
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
m = u->mixent_list;
while (1) {
uaudio_mixer_skip(&m);
if (m == NULL)
break;
if (index == i) {
*ru = u;
*rm = m;
return 1;
}
if (m->type == UAUDIO_MIX_NUM) {
name = m->fname;
while (m != NULL &&
strcmp(name, m->fname) == 0)
m = m->next;
} else
m = m->next;
i++;
}
}
return 0;
}
int
uaudio_process_header(struct uaudio_softc *sc, struct uaudio_blob *p)
{
struct uaudio_blob ph;
unsigned int type, subtype;
if (!uaudio_getdesc(p, &ph))
return 0;
if (!uaudio_getnum(&ph, 1, &type))
return 0;
if (type != UDESC_CS_INTERFACE) {
DPRINTF("%s: expected cs iface desc\n", __func__);
return 0;
}
if (!uaudio_getnum(&ph, 1, &subtype))
return 0;
if (subtype != UAUDIO_AC_HEADER) {
DPRINTF("%s: expected header desc\n", __func__);
return 0;
}
if (!uaudio_getnum(&ph, 2, &sc->version))
return 0;
DPRINTF("%s: version 0x%x\n", __func__, sc->version);
return 1;
}
int
uaudio_process_ac_ep(struct uaudio_softc *sc, struct uaudio_blob *p)
{
#ifdef UAUDIO_DEBUG
static const char *xfer[] = {
"ctl", "iso", "bulk", "intr"
};
#endif
struct uaudio_blob dp;
unsigned int type, addr, attr, maxpkt, ival;
unsigned char *savepos;
if (p->rptr == p->wptr)
return 1;
savepos = p->rptr;
if (!uaudio_getdesc(p, &dp))
return 0;
if (!uaudio_getnum(&dp, 1, &type))
return 0;
if (type != UDESC_ENDPOINT) {
p->rptr = savepos;
return 1;
}
if (!uaudio_getnum(&dp, 1, &addr))
return 0;
if (!uaudio_getnum(&dp, 1, &attr))
return 0;
if (!uaudio_getnum(&dp, 2, &maxpkt))
return 0;
if (!uaudio_getnum(&dp, 1, &ival))
return 0;
DPRINTF("%s: addr = 0x%x, type = %s, maxpkt = %d, ival = %d\n",
__func__, addr, xfer[UE_GET_XFERTYPE(attr)],
UE_GET_SIZE(maxpkt), ival);
return 1;
}
int
uaudio_process_ac(struct uaudio_softc *sc, struct uaudio_blob *p, int ifnum)
{
struct uaudio_blob units, pu;
struct uaudio_unit *u, *v;
unsigned char *savepos;
unsigned int type, subtype, id;
char *name, val;
DPRINTF("%s: ifnum = %d, %zd bytes to process\n", __func__,
ifnum, p->wptr - p->rptr);
sc->ctl_ifnum = ifnum;
if (!uaudio_process_header(sc, p))
return 0;
savepos = p->rptr;
units.rptr = units.wptr = p->rptr;
while (p->rptr != p->wptr) {
if (!uaudio_getdesc(p, &pu))
return 0;
if (!uaudio_getnum(&pu, 1, &type))
return 0;
if (type != UDESC_CS_INTERFACE)
break;
units.wptr = p->rptr;
}
p->rptr = savepos;
while (p->rptr != units.wptr) {
if (!uaudio_getdesc(p, &pu))
return 0;
if (!uaudio_getnum(&pu, 1, &type))
return 0;
if (!uaudio_getnum(&pu, 1, &subtype))
return 0;
if (subtype == UAUDIO_AC_OUTPUT) {
if (!uaudio_getnum(&pu, 1, &id))
return 0;
if (!uaudio_process_unit(sc, NULL, id, units, NULL))
return 0;
}
}
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
switch (u->type) {
case UAUDIO_AC_INPUT:
uaudio_mkname(sc, uaudio_tname(sc, u->term, 0), u->name);
break;
case UAUDIO_AC_OUTPUT:
uaudio_mkname(sc, uaudio_tname(sc, u->term, 1), u->name);
break;
case UAUDIO_AC_CLKSRC:
uaudio_mkname(sc, uaudio_clkname(u->term), u->name);
break;
case UAUDIO_AC_CLKSEL:
uaudio_mkname(sc, "clksel", u->name);
break;
case UAUDIO_AC_EFFECT:
uaudio_mkname(sc, "fx", u->name);
break;
case UAUDIO_AC_PROCESSING:
uaudio_mkname(sc, "proc", u->name);
break;
case UAUDIO_AC_EXTENSION:
uaudio_mkname(sc, "ext", u->name);
break;
}
}
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
if (u->type != UAUDIO_AC_MIXER &&
u->type != UAUDIO_AC_SELECTOR)
continue;
if (!uaudio_setname_dsts(sc, u, NULL)) {
switch (u->type) {
case UAUDIO_AC_MIXER:
name = "mix";
break;
case UAUDIO_AC_SELECTOR:
name = "sel";
break;
}
uaudio_mkname(sc, name, u->name);
}
}
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
if (u->type != UAUDIO_AC_FEATURE)
continue;
if (uaudio_setname_dsts(sc, u, UAUDIO_NAME_REC)) {
u->mixer_class = UAUDIO_CLASS_IN;
continue;
}
if (uaudio_setname_srcs(sc, u, UAUDIO_NAME_PLAY)) {
u->mixer_class = UAUDIO_CLASS_OUT;
continue;
}
if (uaudio_setname_dsts(sc, u, NULL)) {
u->mixer_class = UAUDIO_CLASS_OUT;
continue;
}
if (uaudio_setname_srcs(sc, u, NULL)) {
u->mixer_class = UAUDIO_CLASS_IN;
continue;
}
uaudio_setname_middle(sc, u);
u->mixer_class = UAUDIO_CLASS_IN;
}
#ifdef UAUDIO_DEBUG
if (uaudio_debug) {
printf("%s: units list:\n", DEVNAME(sc));
for (u = sc->unit_list; u != NULL; u = u->unit_next)
uaudio_print_unit(sc, u);
printf("%s: mixer controls:\n", DEVNAME(sc));
uaudio_mixer_print(sc);
}
#endif
if (!uaudio_process_ac_ep(sc, p))
return 0;
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
switch (u->type) {
case UAUDIO_AC_CLKSRC:
if (!uaudio_req_ranges(sc, 4,
UAUDIO_V2_REQSEL_CLKFREQ,
0,
sc->ctl_ifnum,
u->id,
&u->rates)) {
printf("%s: failed to read clock rates\n",
DEVNAME(sc));
return 0;
}
#ifdef UAUDIO_DEBUG
if (uaudio_debug) {
printf("%02u: clock rates: ", u->id);
uaudio_ranges_print(&u->rates);
if (!u->cap_freqctl)
printf("%02u: no rate control\n", u->id);
}
#endif
break;
case UAUDIO_AC_CLKSEL:
if (!uaudio_req(sc, UT_READ_CLASS_INTERFACE,
UAUDIO_V2_REQ_CUR,
UAUDIO_V2_REQSEL_CLKSEL, 0,
sc->ctl_ifnum, u->id,
&val, 1)) {
printf("%s: failed to read clock selector\n",
DEVNAME(sc));
return 0;
}
for (v = u->src_list; v != NULL; v = v->src_next) {
if (--val == 0)
break;
}
u->clock = v;
break;
}
}
if (sc->version == UAUDIO_V2) {
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
if (u->type == UAUDIO_AC_INPUT && (u->term >> 8) == 1) {
if (u->clock == NULL) {
printf("%s: no play clock\n", DEVNAME(sc));
return 0;
}
sc->pclock = u->clock;
break;
}
}
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
if (u->type == UAUDIO_AC_OUTPUT && (u->term >> 8) == 1) {
if (u->clock == NULL) {
printf("%s: no rec clock\n", DEVNAME(sc));
return 0;
}
sc->rclock = u->clock;
break;
}
}
DPRINTF("%s: pclock = %d, rclock = %d\n", __func__,
sc->pclock ? sc->pclock->id : -1,
sc->rclock ? sc->rclock->id : -1);
if (!uaudio_clock_equiv(sc, sc->pclock, sc->rclock))
return 0;
}
return 1;
}
int
uaudio_process_as_ep(struct uaudio_softc *sc,
struct uaudio_blob *p, struct uaudio_alt *a, int nep)
{
unsigned int addr, attr, maxpkt, isotype, ival;
if (!uaudio_getnum(p, 1, &addr))
return 0;
if (!uaudio_getnum(p, 1, &attr))
return 0;
if (!uaudio_getnum(p, 2, &maxpkt))
return 0;
if (!uaudio_getnum(p, 1, &ival))
return 0;
DPRINTF("%s: addr = 0x%x, %s/%s, "
"maxpktsz = %d, ival = %d\n",
__func__, addr,
uaudio_isoname(UE_GET_ISO_TYPE(attr)),
uaudio_usagename(UE_GET_ISO_USAGE(attr)),
maxpkt, ival);
if (UE_GET_XFERTYPE(attr) != UE_ISOCHRONOUS) {
printf("%s: skipped non-isoc endpt.\n", DEVNAME(sc));
return 1;
}
isotype = UE_GET_ISO_TYPE(attr);
if (isotype || nep == 1) {
if (a->data_addr && addr != a->data_addr) {
printf("%s: skipped extra data endpt.\n", DEVNAME(sc));
return 1;
}
a->mode = (UE_GET_DIR(addr) == UE_DIR_IN) ?
AUMODE_RECORD : AUMODE_PLAY;
a->data_addr = addr;
a->fps = sc->ufps / (1 << (ival - 1));
a->maxpkt = UE_GET_SIZE(maxpkt);
} else {
if (a->sync_addr && addr != a->sync_addr) {
printf("%s: skipped extra sync endpt.\n", DEVNAME(sc));
return 1;
}
a->sync_addr = addr;
}
return 1;
}
int
uaudio_process_as_cs_ep(struct uaudio_softc *sc,
struct uaudio_blob *p, struct uaudio_alt *a, int nep)
{
unsigned int subtype, attr;
if (!uaudio_getnum(p, 1, &subtype))
return 0;
if (subtype != UAUDIO_AS_EP_GENERAL) {
DPRINTF("%s: %d: bad cs ep subtype\n", __func__, subtype);
return 0;
}
if (!uaudio_getnum(p, 1, &attr))
return 0;
if (sc->version == UAUDIO_V1) {
a->v1_cap_freqctl = !!(attr & UAUDIO_EP_FREQCTL);
if (!a->v1_cap_freqctl)
DPRINTF("alt %d: no rate control\n", a->altnum);
}
return 1;
}
int
uaudio_process_as_general(struct uaudio_softc *sc,
struct uaudio_blob *p, int *rispcm, struct uaudio_alt *a)
{
unsigned int term, fmt, ctl, fmt_type, fmt_map, nch;
if (!uaudio_getnum(p, 1, &term))
return 0;
switch (sc->version) {
case UAUDIO_V1:
if (!uaudio_getnum(p, 1, NULL))
return 0;
if (!uaudio_getnum(p, 1, &fmt))
return 0;
*rispcm = (fmt == UAUDIO_V1_FMT_PCM);
break;
case UAUDIO_V2:
if (!uaudio_getnum(p, 1, &ctl))
return 0;
if (!uaudio_getnum(p, 1, &fmt_type))
return 0;
if (!uaudio_getnum(p, 4, &fmt_map))
return 0;
if (!uaudio_getnum(p, 1, &nch))
return 0;
a->nch = nch;
*rispcm = (fmt_type == 1) && (fmt_map & UAUDIO_V2_FMT_PCM);
}
return 1;
}
int
uaudio_process_as_format(struct uaudio_softc *sc,
struct uaudio_blob *p, struct uaudio_alt *a, int *ispcm)
{
unsigned int type, bps, bits, nch, nrates, rate_min, rate_max, rates;
int i, j;
switch (sc->version) {
case UAUDIO_V1:
if (!uaudio_getnum(p, 1, &type))
return 0;
if (type != 1) {
DPRINTF("%s: class v1: "
"skipped unsupported type = %d\n", __func__, type);
*ispcm = 0;
return 1;
}
if (!uaudio_getnum(p, 1, &nch))
return 0;
if (!uaudio_getnum(p, 1, &bps))
return 0;
if (!uaudio_getnum(p, 1, &bits))
return 0;
if (!uaudio_getnum(p, 1, &nrates))
return 0;
rates = 0;
if (nrates == 0) {
if (!uaudio_getnum(p, 3, &rate_min))
return 0;
if (!uaudio_getnum(p, 3, &rate_max))
return 0;
for (i = 0; i < nitems(uaudio_rates); i++) {
if (uaudio_rates[i] >= rate_min &&
uaudio_rates[i] <= rate_max)
rates |= 1 << i;
}
} else {
for (j = 0; j < nrates; j++) {
if (!uaudio_getnum(p, 3, &rate_min))
return 0;
for (i = 0; i < nitems(uaudio_rates); i++) {
if (uaudio_rates[i] == rate_min)
rates |= 1 << i;
}
}
}
a->v1_rates = rates;
a->nch = nch;
break;
case UAUDIO_V2:
if (!uaudio_getnum(p, 1, &type))
return 0;
if (type != 1) {
DPRINTF("%s: class v2: "
"skipped unsupported type = %d\n", __func__, type);
*ispcm = 0;
return 1;
}
if (!uaudio_getnum(p, 1, &bps))
return 0;
if (!uaudio_getnum(p, 1, &bits))
return 0;
break;
}
a->bps = bps;
a->bits = bits;
*ispcm = 1;
return 1;
}
int
uaudio_process_as(struct uaudio_softc *sc,
struct uaudio_blob *p, int ifnum, int altnum, int nep)
{
struct uaudio_alt *a, *anext, **pa;
struct uaudio_blob dp;
unsigned char *savep;
unsigned int type, subtype;
int ispcm = 0;
a = malloc(sizeof(struct uaudio_alt), M_USBDEV, M_WAITOK);
a->mode = 0;
a->nch = 0;
a->v1_cap_freqctl = 0;
a->v1_rates = 0;
a->data_addr = 0;
a->sync_addr = 0;
a->ifnum = ifnum;
a->altnum = altnum;
while (p->rptr != p->wptr) {
savep = p->rptr;
if (!uaudio_getdesc(p, &dp))
goto failed;
if (!uaudio_getnum(&dp, 1, &type))
goto failed;
if (type != UDESC_CS_INTERFACE) {
p->rptr = savep;
break;
}
if (!uaudio_getnum(&dp, 1, &subtype))
goto failed;
switch (subtype) {
case UAUDIO_AS_GENERAL:
if (!uaudio_process_as_general(sc, &dp, &ispcm, a))
goto failed;
break;
case UAUDIO_AS_FORMAT:
if (!uaudio_process_as_format(sc, &dp, a, &ispcm))
goto failed;
break;
default:
DPRINTF("%s: unknown desc\n", __func__);
continue;
}
if (!ispcm) {
DPRINTF("%s: non-pcm iface\n", __func__);
free(a, M_USBDEV, sizeof(struct uaudio_alt));
return 1;
}
}
while (p->rptr != p->wptr) {
savep = p->rptr;
if (!uaudio_getdesc(p, &dp))
goto failed;
if (!uaudio_getnum(&dp, 1, &type))
goto failed;
if (type == UDESC_CS_ENDPOINT) {
if (!uaudio_process_as_cs_ep(sc, &dp, a, nep))
goto failed;
} else if (type == UDESC_ENDPOINT) {
if (!uaudio_process_as_ep(sc, &dp, a, nep))
goto failed;
} else {
p->rptr = savep;
break;
}
}
if (a->mode == 0) {
printf("%s: no data endpoints found\n", DEVNAME(sc));
free(a, M_USBDEV, sizeof(struct uaudio_alt));
return 1;
}
pa = &sc->alts;
while (1) {
if ((anext = *pa) == NULL)
break;
if (a->nch > anext->nch)
break;
else if (a->nch == anext->nch) {
if (a->bits > anext->bits)
break;
else if (sc->version == UAUDIO_V1 &&
a->v1_rates > anext->v1_rates)
break;
}
pa = &anext->next;
}
a->next = *pa;
*pa = a;
return 1;
failed:
free(a, M_USBDEV, sizeof(struct uaudio_alt));
return 0;
}
void
uaudio_fixup_params(struct uaudio_softc *sc)
{
struct uaudio_alt *ap, *ar, *a;
struct uaudio_params *p, **pp;
int rates;
pp = &sc->params_list;
for (ap = sc->alts; ap != NULL; ap = ap->next) {
if (ap->mode != AUMODE_PLAY)
continue;
for (ar = sc->alts; ar != NULL; ar = ar->next) {
if (ar->mode != AUMODE_RECORD)
continue;
if (ar->bps != ap->bps || ar->bits != ap->bits)
continue;
switch (sc->version) {
case UAUDIO_V1:
rates = ap->v1_rates & ar->v1_rates;
if (rates == 0)
continue;
break;
case UAUDIO_V2:
rates = 0;
break;
}
p = malloc(sizeof(struct uaudio_params),
M_USBDEV, M_WAITOK);
p->palt = ap;
p->ralt = ar;
p->v1_rates = rates;
p->next = NULL;
*pp = p;
pp = &p->next;
}
}
if (sc->params_list == NULL) {
for (a = sc->alts; a != NULL; a = a->next) {
p = malloc(sizeof(struct uaudio_params),
M_USBDEV, M_WAITOK);
if (a->mode == AUMODE_PLAY) {
p->palt = a;
p->ralt = NULL;
} else {
p->palt = NULL;
p->ralt = a;
}
p->v1_rates = a->v1_rates;
p->next = NULL;
*pp = p;
pp = &p->next;
}
}
}
int
uaudio_iface_index(struct uaudio_softc *sc, int ifnum)
{
int i, nifaces;
nifaces = sc->udev->cdesc->bNumInterfaces;
for (i = 0; i < nifaces; i++) {
if (sc->udev->ifaces[i].idesc->bInterfaceNumber == ifnum)
return i;
}
printf("%s: %d: invalid interface number\n", __func__, ifnum);
return -1;
}
int
uaudio_process_conf(struct uaudio_softc *sc, struct uaudio_blob *p)
{
struct uaudio_blob dp;
struct uaudio_alt *a;
unsigned int type, ifnum, altnum, nep, class, subclass;
int i;
while (p->rptr != p->wptr) {
if (!uaudio_getdesc(p, &dp))
return 0;
if (!uaudio_getnum(&dp, 1, &type))
return 0;
if (type != UDESC_INTERFACE)
continue;
if (!uaudio_getnum(&dp, 1, &ifnum))
return 0;
if (!uaudio_getnum(&dp, 1, &altnum))
return 0;
if (!uaudio_getnum(&dp, 1, &nep))
return 0;
if (!uaudio_getnum(&dp, 1, &class))
return 0;
if (!uaudio_getnum(&dp, 1, &subclass))
return 0;
if (class != UICLASS_AUDIO) {
DPRINTF("%s: skipped iface\n", __func__);
continue;
}
switch (subclass) {
case UISUBCLASS_AUDIOCONTROL:
i = uaudio_iface_index(sc, ifnum);
if (i != -1 && usbd_iface_claimed(sc->udev, i)) {
DPRINTF("%s: %d: AC already claimed\n", __func__, ifnum);
sc->instnum++;
break;
}
if (sc->unit_list != NULL) {
DPRINTF("%s: >1 AC ifaces\n", __func__);
goto done;
}
if (!uaudio_process_ac(sc, p, ifnum))
return 0;
break;
case UISUBCLASS_AUDIOSTREAM:
i = uaudio_iface_index(sc, ifnum);
if (i != -1 && usbd_iface_claimed(sc->udev, i)) {
DPRINTF("%s: %d: AS already claimed\n", __func__, ifnum);
break;
}
if (nep == 0) {
DPRINTF("%s: stop altnum %d, ifnum %d\n",
__func__, altnum, ifnum);
break;
}
if (!uaudio_process_as(sc, p, ifnum, altnum, nep))
return 0;
}
}
done:
uaudio_fixup_params(sc);
for (a = sc->alts; a != NULL; a = a->next) {
i = uaudio_iface_index(sc, a->ifnum);
if (i != -1) {
DPRINTF("%s: claim: %d at %d\n", __func__, a->ifnum, i);
usbd_claim_iface(sc->udev, i);
}
}
i = uaudio_iface_index(sc, sc->ctl_ifnum);
if (i != -1) {
DPRINTF("%s: claim: ac %d at %d\n", __func__, sc->ctl_ifnum, i);
usbd_claim_iface(sc->udev, i);
}
return 1;
}
int
uaudio_xfer_alloc(struct uaudio_softc *sc, struct uaudio_xfer *xfer,
unsigned int framesize, unsigned int count)
{
xfer->usb_xfer = usbd_alloc_xfer(sc->udev);
if (xfer->usb_xfer == NULL)
return ENOMEM;
xfer->buf = usbd_alloc_buffer(xfer->usb_xfer, framesize * count);
if (xfer->buf == NULL)
return ENOMEM;
xfer->sizes = mallocarray(count,
sizeof(xfer->sizes[0]), M_USBDEV, M_WAITOK);
if (xfer->sizes == NULL)
return ENOMEM;
return 0;
}
void
uaudio_xfer_free(struct uaudio_softc *sc, struct uaudio_xfer *xfer,
unsigned int count)
{
if (xfer->usb_xfer != NULL) {
usbd_free_xfer(xfer->usb_xfer);
xfer->usb_xfer = NULL;
}
if (xfer->sizes != NULL) {
free(xfer->sizes, M_USBDEV,
sizeof(xfer->sizes[0]) * count);
xfer->sizes = NULL;
}
}
void
uaudio_stream_close(struct uaudio_softc *sc, int dir)
{
struct uaudio_stream *s = &sc->pstream;
struct uaudio_alt *a = sc->params->palt;
struct usbd_interface *iface;
int err, i;
if (dir == AUMODE_PLAY) {
s = &sc->pstream;
a = sc->params->palt;
} else {
s = &sc->rstream;
a = sc->params->ralt;
}
if (s->data_pipe) {
usbd_close_pipe(s->data_pipe);
s->data_pipe = NULL;
}
if (s->sync_pipe) {
usbd_close_pipe(s->sync_pipe);
s->sync_pipe = NULL;
}
err = usbd_device2interface_handle(sc->udev, a->ifnum, &iface);
if (err)
printf("%s: can't get iface handle\n", DEVNAME(sc));
else {
err = usbd_set_interface(iface, 0);
if (err)
printf("%s: can't reset interface\n", DEVNAME(sc));
}
for (i = 0; i < UAUDIO_NXFERS_MAX; i++) {
uaudio_xfer_free(sc, s->data_xfers + i, s->nframes_max);
uaudio_xfer_free(sc, s->sync_xfers + i, 1);
}
}
int
uaudio_stream_open(struct uaudio_softc *sc, int dir,
void *start, void *end, size_t blksz, void (*intr)(void *), void *arg)
{
struct uaudio_stream *s;
struct uaudio_alt *a;
struct uaudio_unit *clock;
struct usbd_interface *iface;
unsigned char req_buf[4];
unsigned int bpa, spf_max, min_blksz;
int err, i;
if (dir == AUMODE_PLAY) {
s = &sc->pstream;
a = sc->params->palt;
} else {
s = &sc->rstream;
a = sc->params->ralt;
}
for (i = 0; i < UAUDIO_NXFERS_MAX; i++) {
s->data_xfers[i].usb_xfer = NULL;
s->data_xfers[i].sizes = NULL;
s->sync_xfers[i].usb_xfer = NULL;
s->sync_xfers[i].sizes = NULL;
}
s->data_pipe = NULL;
s->sync_pipe = NULL;
s->nframes_mask = 0;
i = a->fps;
while (i > 1000) {
s->nframes_mask = (s->nframes_mask << 1) | 1;
i >>= 1;
}
bpa = a->bps * a->nch;
s->spf = (uint64_t)sc->rate * UAUDIO_SPF_DIV / a->fps;
s->spf_min = (uint64_t)s->spf * 255 / 256;
s->spf_max = (uint64_t)s->spf * 257 / 256;
spf_max = (a->maxpkt / bpa) * UAUDIO_SPF_DIV;
if (s->spf > spf_max) {
printf("%s: samples per frame too large\n", DEVNAME(sc));
return EIO;
}
if (s->spf_max > spf_max)
s->spf_max = spf_max;
min_blksz = (((uint64_t)blksz << 32) / s->spf_max * s->spf_min) >> 32;
min_blksz -= min_blksz % bpa;
s->safe_blksz = blksz + UAUDIO_NXFERS_MAX * (blksz - min_blksz);
s->nframes_max = (uint64_t)(s->safe_blksz / bpa) *
UAUDIO_SPF_DIV / s->spf_min + 1;
s->nframes_max = (s->nframes_max + s->nframes_mask) &
~s->nframes_mask;
s->maxpkt = bpa *
((s->spf_max + UAUDIO_SPF_DIV - 1) / UAUDIO_SPF_DIV);
s->nxfers = sc->host_nframes / s->nframes_max;
if (s->nxfers > UAUDIO_NXFERS_MAX)
s->nxfers = UAUDIO_NXFERS_MAX;
DPRINTF("%s: %s: blksz = %zu, rate = %u, fps = %u\n", __func__,
dir == AUMODE_PLAY ? "play" : "rec", blksz, sc->rate, a->fps);
DPRINTF("%s: spf = 0x%x in [0x%x:0x%x]\n", __func__,
s->spf, s->spf_min, s->spf_max);
DPRINTF("%s: nframes_max = %u, nframes_mask = %u, maxpkt = %u\n",
__func__, s->nframes_max, s->nframes_mask, s->maxpkt);
DPRINTF("%s: safe_blksz = %d, nxfers = %d\n", __func__,
s->safe_blksz, s->nxfers);
if (s->nxfers < UAUDIO_NXFERS_MIN) {
printf("%s: block size too large\n", DEVNAME(sc));
return EIO;
}
if (1000 * blksz < 2 * sc->rate * bpa) {
printf("%s: audio block too small\n", DEVNAME(sc));
return EIO;
}
for (i = 0; i < s->nxfers; i++) {
err = uaudio_xfer_alloc(sc, s->data_xfers + i,
s->maxpkt, s->nframes_max);
if (err)
goto failed;
if (a->sync_addr) {
err = uaudio_xfer_alloc(sc, s->sync_xfers + i,
sc->sync_pktsz, 1);
if (err)
goto failed;
}
}
err = usbd_device2interface_handle(sc->udev, a->ifnum, &iface);
if (err) {
printf("%s: can't get iface handle\n", DEVNAME(sc));
goto failed;
}
err = usbd_set_interface(iface, a->altnum);
if (err) {
printf("%s: can't set interface\n", DEVNAME(sc));
goto failed;
}
switch (sc->version) {
case UAUDIO_V1:
if (!a->v1_cap_freqctl) {
DPRINTF("%s: not setting endpoint rate\n", __func__);
break;
}
req_buf[0] = sc->rate;
req_buf[1] = sc->rate >> 8;
req_buf[2] = sc->rate >> 16;
if (!uaudio_req(sc, UT_WRITE_CLASS_ENDPOINT,
UAUDIO_V1_REQ_SET_CUR, UAUDIO_REQSEL_RATE, 0,
a->data_addr, 0, req_buf, 3)) {
printf("%s: failed to set endpoint rate\n", DEVNAME(sc));
}
break;
case UAUDIO_V2:
req_buf[0] = sc->rate;
req_buf[1] = sc->rate >> 8;
req_buf[2] = sc->rate >> 16;
req_buf[3] = sc->rate >> 24;
clock = uaudio_clock(dir == AUMODE_PLAY ? sc->pclock : sc->rclock);
if (clock == NULL) {
printf("%s: can't get clock\n", DEVNAME(sc));
goto failed;
}
if (!clock->cap_freqctl) {
DPRINTF("%s: not setting clock rate\n", __func__);
break;
}
if (!uaudio_req(sc, UT_WRITE_CLASS_INTERFACE,
UAUDIO_V2_REQ_CUR, UAUDIO_REQSEL_RATE, 0,
sc->ctl_ifnum, clock->id, req_buf, 4)) {
printf("%s: failed to set clock rate\n", DEVNAME(sc));
}
break;
}
err = usbd_open_pipe(iface, a->data_addr, 0, &s->data_pipe);
if (err) {
printf("%s: can't open data pipe\n", DEVNAME(sc));
goto failed;
}
if (a->sync_addr) {
err = usbd_open_pipe(iface, a->sync_addr, 0, &s->sync_pipe);
if (err) {
printf("%s: can't open sync pipe\n", DEVNAME(sc));
goto failed;
}
}
s->data_nextxfer = 0;
s->sync_nextxfer = 0;
s->spf_remain = 0;
s->intr = intr;
s->arg = arg;
s->ring_start = start;
s->ring_end = end;
s->ring_blksz = blksz;
s->ring_pos = s->ring_start;
s->ring_offs = 0;
s->ring_icnt = 0;
s->ubuf_xfer = 0;
s->ubuf_pos = 0;
return 0;
failed:
uaudio_stream_close(sc, dir);
return ENOMEM;
}
void
uaudio_adjspf(struct uaudio_softc *sc)
{
struct uaudio_stream *s = &sc->pstream;
int diff;
if (sc->mode != (AUMODE_RECORD | AUMODE_PLAY))
return;
if (s->sync_pipe != NULL)
return;
diff = sc->diff_nsamp;
if (sc->diff_nframes > 0) {
diff -= (uint64_t)sc->pstream.spf *
sc->diff_nframes / UAUDIO_SPF_DIV;
} else {
diff += (uint64_t)sc->rstream.spf *
-sc->diff_nframes / UAUDIO_SPF_DIV;
}
s->spf = (uint64_t)(sc->rate - diff) * UAUDIO_SPF_DIV / sc->ufps;
if (s->spf > s->spf_max)
s->spf = s->spf_max;
else if (s->spf < s->spf_min)
s->spf = s->spf_min;
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 2)
printf("%s: diff = %d, spf = 0x%x\n", __func__, diff, s->spf);
#endif
}
void
uaudio_pdata_copy(struct uaudio_softc *sc)
{
struct uaudio_stream *s = &sc->pstream;
struct uaudio_xfer *xfer;
size_t count, avail;
int index;
#ifdef UAUDIO_DEBUG
struct timeval tv;
getmicrotime(&tv);
#endif
while (sc->copy_todo > 0 && s->ubuf_xfer < s->nxfers) {
index = s->data_nextxfer + s->ubuf_xfer;
if (index >= s->nxfers)
index -= s->nxfers;
xfer = s->data_xfers + index;
avail = s->ring_end - s->ring_pos;
count = xfer->size - s->ubuf_pos;
if (count > avail)
count = avail;
if (count > sc->copy_todo)
count = sc->copy_todo;
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 2) {
printf("%s: %llu.%06lu: %zd..%zd -> %u:%u..%zu\n",
__func__, tv.tv_sec, tv.tv_usec,
s->ring_pos - s->ring_start,
s->ring_pos - s->ring_start + count,
s->ubuf_xfer, s->ubuf_pos, s->ubuf_pos + count);
}
#endif
memcpy(xfer->buf + s->ubuf_pos, s->ring_pos, count);
sc->copy_todo -= count;
s->ring_pos += count;
if (s->ring_pos == s->ring_end) {
s->ring_pos = s->ring_start;
}
s->ubuf_pos += count;
if (s->ubuf_pos == xfer->size) {
usb_syncmem(&xfer->usb_xfer->dmabuf, 0, xfer->size,
BUS_DMASYNC_PREWRITE);
s->ubuf_pos = 0;
#ifdef DIAGNOSTIC
if (s->ubuf_xfer == s->nxfers) {
printf("%s: overflow\n", __func__);
return;
}
#endif
s->ubuf_xfer++;
}
}
}
void
uaudio_pdata_calcsizes(struct uaudio_softc *sc, struct uaudio_xfer *xfer)
{
#ifdef UAUDIO_DEBUG
struct timeval tv;
#endif
struct uaudio_stream *s = &sc->pstream;
struct uaudio_alt *a = sc->params->palt;
unsigned int fsize, bpf;
int done;
bpf = a->bps * a->nch;
done = s->ring_offs;
xfer->nframes = 0;
while (1) {
if ((xfer->nframes & s->nframes_mask) == 0 &&
done > s->safe_blksz)
break;
if (xfer->nframes == s->nframes_max) {
printf("%s: too many frames for play xfer: "
"done = %u, blksz = %d\n",
DEVNAME(sc), done, s->ring_blksz);
break;
}
s->spf_remain += s->spf;
fsize = s->spf_remain / UAUDIO_SPF_DIV * bpf;
s->spf_remain %= UAUDIO_SPF_DIV;
done += fsize;
xfer->sizes[xfer->nframes] = fsize;
xfer->nframes++;
}
xfer->size = done - s->ring_offs;
s->ring_offs = done - s->ring_blksz;
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 3) {
getmicrotime(&tv);
printf("%s: size = %d, offs -> %d\n", __func__,
xfer->size, s->ring_offs);
}
#endif
memset(xfer->buf, 0, xfer->size);
}
void
uaudio_pdata_xfer(struct uaudio_softc *sc)
{
#ifdef UAUDIO_DEBUG
struct timeval tv;
#endif
struct uaudio_stream *s = &sc->pstream;
struct uaudio_xfer *xfer;
int err;
xfer = s->data_xfers + s->data_nextxfer;
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 3) {
getmicrotime(&tv);
printf("%s: %llu.%06lu: "
"%d bytes, %u frames, remain = 0x%x, offs = %d\n",
__func__, tv.tv_sec, tv.tv_usec,
xfer->size, xfer->nframes,
s->spf_remain, s->ring_offs);
}
#endif
if (xfer->nframes == 0) {
printf("%s: zero frame play xfer\n", DEVNAME(sc));
return;
}
usbd_setup_isoc_xfer(xfer->usb_xfer, s->data_pipe, sc,
xfer->sizes, xfer->nframes,
USBD_NO_COPY | USBD_SHORT_XFER_OK,
uaudio_pdata_intr);
err = usbd_transfer(xfer->usb_xfer);
if (err != 0 && err != USBD_IN_PROGRESS)
printf("%s: play xfer, err = %d\n", DEVNAME(sc), err);
if (++s->data_nextxfer == s->nxfers)
s->data_nextxfer = 0;
}
void
uaudio_pdata_intr(struct usbd_xfer *usb_xfer, void *arg, usbd_status status)
{
#ifdef UAUDIO_DEBUG
struct timeval tv;
#endif
struct uaudio_softc *sc = arg;
struct uaudio_stream *s = &sc->pstream;
struct uaudio_xfer *xfer;
uint32_t size;
int nintr;
if (status != 0 && status != USBD_IOERROR) {
DPRINTF("%s: xfer status = %d\n", __func__, status);
return;
}
xfer = s->data_xfers + s->data_nextxfer;
if (xfer->usb_xfer != usb_xfer) {
DPRINTF("%s: wrong xfer\n", __func__);
return;
}
sc->diff_nsamp += xfer->size /
(sc->params->palt->nch * sc->params->palt->bps);
sc->diff_nframes += xfer->nframes;
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 2) {
getmicrotime(&tv);
printf("%s: %llu.%06lu: %u: %u bytes\n",
__func__, tv.tv_sec, tv.tv_usec,
s->data_nextxfer, xfer->size);
}
#endif
usbd_get_xfer_status(usb_xfer, NULL, NULL, &size, NULL);
if (size != xfer->size) {
DPRINTF("%s: %u bytes out of %u: incomplete play xfer\n",
DEVNAME(sc), size, xfer->size);
}
s->ring_icnt += xfer->size;
nintr = 0;
mtx_enter(&audio_lock);
while (s->ring_icnt >= s->ring_blksz) {
s->intr(s->arg);
s->ring_icnt -= s->ring_blksz;
nintr++;
}
mtx_leave(&audio_lock);
if (nintr != 1)
printf("%s: %d: bad play intr count\n", __func__, nintr);
uaudio_pdata_calcsizes(sc, xfer);
uaudio_pdata_xfer(sc);
#ifdef DIAGNOSTIC
if (s->ubuf_xfer == 0) {
printf("%s: underflow\n", __func__);
return;
}
#endif
s->ubuf_xfer--;
uaudio_pdata_copy(sc);
}
void
uaudio_psync_xfer(struct uaudio_softc *sc)
{
#ifdef UAUDIO_DEBUG
struct timeval tv;
#endif
struct uaudio_stream *s = &sc->pstream;
struct uaudio_xfer *xfer;
unsigned int i;
int err;
xfer = s->sync_xfers + s->sync_nextxfer;
xfer->nframes = 1;
for (i = 0; i < xfer->nframes; i++)
xfer->sizes[i] = sc->sync_pktsz;
xfer->size = xfer->nframes * sc->sync_pktsz;
#ifdef UAUDIO_DEBUG
memset(xfer->buf, 0xd0, sc->sync_pktsz * xfer->nframes);
#endif
usbd_setup_isoc_xfer(xfer->usb_xfer, s->sync_pipe, sc,
xfer->sizes, xfer->nframes,
USBD_NO_COPY | USBD_SHORT_XFER_OK,
uaudio_psync_intr);
err = usbd_transfer(xfer->usb_xfer);
if (err != 0 && err != USBD_IN_PROGRESS)
printf("%s: sync play xfer, err = %d\n", DEVNAME(sc), err);
if (++s->sync_nextxfer == s->nxfers)
s->sync_nextxfer = 0;
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 3) {
getmicrotime(&tv);
printf("%s: %llu.%06lu: %dB, %d fr\n", __func__,
tv.tv_sec, tv.tv_usec, sc->sync_pktsz, xfer->nframes);
}
#endif
}
void
uaudio_psync_intr(struct usbd_xfer *usb_xfer, void *arg, usbd_status status)
{
#ifdef UAUDIO_DEBUG
struct timeval tv;
#endif
struct uaudio_softc *sc = arg;
struct uaudio_stream *s = &sc->pstream;
struct uaudio_xfer *xfer;
unsigned char *buf;
unsigned int i;
int32_t val;
if (status != 0) {
DPRINTF("%s: xfer status = %d\n", __func__, status);
return;
}
xfer = s->sync_xfers + s->sync_nextxfer;
if (xfer->usb_xfer != usb_xfer) {
DPRINTF("%s: wrong xfer\n", __func__);
return;
}
buf = xfer->buf;
for (i = 0; i < xfer->nframes; i++) {
if (xfer->sizes[i] == sc->sync_pktsz) {
val = buf[0] | buf[1] << 8 | buf[2] << 16;
if (sc->sync_pktsz == 4)
val |= xfer->buf[3] << 24;
else
val <<= 2;
val *= UAUDIO_SPF_DIV / (1 << 16);
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 2) {
getmicrotime(&tv);
printf("%s: %llu.%06lu: spf: %08x\n",
__func__, tv.tv_sec, tv.tv_usec, val);
}
#endif
if (val > s->spf_max)
s->spf = s->spf_max;
else if (val < s->spf_min)
s->spf = s->spf_min;
else
s->spf = val;
}
buf += sc->sync_pktsz;
}
uaudio_psync_xfer(sc);
}
void
uaudio_rdata_xfer(struct uaudio_softc *sc)
{
#ifdef UAUDIO_DEBUG
struct timeval tv;
#endif
struct uaudio_stream *s = &sc->rstream;
struct uaudio_alt *a = sc->params->ralt;
struct uaudio_xfer *xfer;
unsigned int fsize, bpf;
int done;
int err;
xfer = s->data_xfers + s->data_nextxfer;
bpf = a->bps * a->nch;
xfer->nframes = 0;
done = s->ring_offs;
while (1) {
if ((xfer->nframes & s->nframes_mask) == 0 &&
done > s->safe_blksz) {
done:
xfer->size = done - s->ring_offs;
s->ring_offs = done - s->ring_blksz;
break;
}
if (xfer->nframes == s->nframes_max) {
printf("%s: too many frames for rec xfer: "
"done = %d, blksz = %d\n",
DEVNAME(sc), done, s->ring_blksz);
goto done;
}
s->spf_remain += s->spf;
fsize = s->spf_remain / UAUDIO_SPF_DIV * bpf;
s->spf_remain %= UAUDIO_SPF_DIV;
done += fsize;
xfer->sizes[xfer->nframes] = s->maxpkt;
xfer->nframes++;
}
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 3) {
getmicrotime(&tv);
printf("%s: %llu.%06lu: "
"%u fr, %d bytes (max %d), offs = %d\n",
__func__, tv.tv_sec, tv.tv_usec,
xfer->nframes, xfer->size,
s->maxpkt * xfer->nframes, s->ring_offs);
}
#endif
if (xfer->nframes == 0) {
printf("%s: zero frame rec xfer\n", DEVNAME(sc));
return;
}
#ifdef UAUDIO_DEBUG
memset(xfer->buf, 0xd0, s->maxpkt * xfer->nframes);
#endif
usbd_setup_isoc_xfer(xfer->usb_xfer, s->data_pipe, sc,
xfer->sizes, xfer->nframes, USBD_NO_COPY | USBD_SHORT_XFER_OK,
uaudio_rdata_intr);
err = usbd_transfer(xfer->usb_xfer);
if (err != 0 && err != USBD_IN_PROGRESS)
printf("%s: rec xfer, err = %d\n", DEVNAME(sc), err);
if (++s->data_nextxfer == s->nxfers)
s->data_nextxfer = 0;
}
void
uaudio_rdata_intr(struct usbd_xfer *usb_xfer, void *arg, usbd_status status)
{
#ifdef UAUDIO_DEBUG
struct timeval tv;
#endif
struct uaudio_softc *sc = arg;
struct uaudio_stream *s = &sc->rstream;
struct uaudio_alt *a = sc->params->ralt;
struct uaudio_xfer *xfer;
unsigned char *buf, *framebuf;
unsigned int count, fsize, fsize_min, nframes, bpf;
unsigned int data_size, null_count;
unsigned int nintr;
if (status != 0) {
DPRINTF("%s: xfer status = %d\n", __func__, status);
return;
}
xfer = s->data_xfers + s->data_nextxfer;
if (xfer->usb_xfer != usb_xfer) {
DPRINTF("%s: wrong xfer\n", __func__);
return;
}
bpf = a->bps * a->nch;
framebuf = xfer->buf;
nframes = 0;
null_count = 0;
data_size = 0;
fsize_min = s->spf_min / UAUDIO_SPF_DIV;
for (nframes = 0; nframes < xfer->nframes; nframes++) {
fsize = xfer->sizes[nframes];
if (fsize < fsize_min) {
s->spf_remain += s->spf;
fsize = s->spf_remain / UAUDIO_SPF_DIV * bpf;
s->spf_remain %= UAUDIO_SPF_DIV;
memset(framebuf, 0, fsize);
null_count++;
}
data_size += fsize;
buf = framebuf;
while (fsize > 0) {
count = s->ring_end - s->ring_pos;
if (count > fsize)
count = fsize;
memcpy(s->ring_pos, buf, count);
s->ring_pos += count;
if (s->ring_pos == s->ring_end)
s->ring_pos = s->ring_start;
buf += count;
fsize -= count;
}
framebuf += s->maxpkt;
}
s->ring_offs += data_size - xfer->size;
s->ring_icnt += data_size;
sc->diff_nsamp -= data_size /
(sc->params->ralt->nch * sc->params->ralt->bps);
sc->diff_nframes -= xfer->nframes;
sc->adjspf_age += xfer->nframes;
if (sc->adjspf_age >= sc->ufps / 8) {
sc->adjspf_age -= sc->ufps / 8;
uaudio_adjspf(sc);
}
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 2) {
getmicrotime(&tv);
printf("%s: %llu.%06lu: %u: "
"%u bytes of %u, offs -> %d\n",
__func__, tv.tv_sec, tv.tv_usec,
s->data_nextxfer, data_size, xfer->size, s->ring_offs);
}
if (null_count > 0) {
DPRINTF("%s: %u null frames out of %u: incomplete record xfer\n",
DEVNAME(sc), null_count, xfer->nframes);
}
#endif
uaudio_rdata_xfer(sc);
nintr = 0;
mtx_enter(&audio_lock);
while (s->ring_icnt >= s->ring_blksz) {
s->intr(s->arg);
s->ring_icnt -= s->ring_blksz;
nintr++;
}
mtx_leave(&audio_lock);
if (nintr != 1)
printf("%s: %u: bad rec intr count\n", DEVNAME(sc), nintr);
}
void
uaudio_trigger(struct uaudio_softc *sc)
{
int i, s;
if (sc->mode != sc->trigger_mode)
return;
DPRINTF("%s: preparing\n", __func__);
if (sc->mode & AUMODE_PLAY) {
for (i = 0; i < sc->pstream.nxfers; i++)
uaudio_pdata_calcsizes(sc, sc->pstream.data_xfers + i);
uaudio_pdata_copy(sc);
}
sc->diff_nsamp = 0;
sc->diff_nframes = 0;
sc->adjspf_age = 0;
DPRINTF("%s: starting\n", __func__);
s = splusb();
for (i = 0; i < UAUDIO_NXFERS_MAX; i++) {
if ((sc->mode & AUMODE_PLAY) && i < sc->pstream.nxfers) {
if (sc->pstream.sync_pipe)
uaudio_psync_xfer(sc);
uaudio_pdata_xfer(sc);
}
if ((sc->mode & AUMODE_RECORD) && i < sc->rstream.nxfers)
uaudio_rdata_xfer(sc);
}
splx(s);
}
void
uaudio_print(struct uaudio_softc *sc)
{
struct uaudio_unit *u;
struct uaudio_mixent *m;
struct uaudio_params *p;
int pchan = 0, rchan = 0, async = 0;
int nctl = 0;
for (u = sc->unit_list; u != NULL; u = u->unit_next) {
m = u->mixent_list;
while (1) {
uaudio_mixer_skip(&m);
if (m == NULL)
break;
m = m->next;
nctl++;
}
}
for (p = sc->params_list; p != NULL; p = p->next) {
if (p->palt && p->palt->nch > pchan)
pchan = p->palt->nch;
if (p->ralt && p->ralt->nch > rchan)
rchan = p->ralt->nch;
if (p->palt && p->palt->sync_addr)
async = 1;
if (p->ralt && p->ralt->sync_addr)
async = 1;
}
printf("%s: class v%d, %s, %s, channels: %d play, %d rec, %d ctls\n",
DEVNAME(sc),
sc->version >> 8,
sc->ufps == 1000 ? "full-speed" : "high-speed",
async ? "async" : "sync",
pchan, rchan, nctl);
}
int
uaudio_match(struct device *parent, void *match, void *aux)
{
struct usb_attach_arg *arg = aux;
struct usb_interface_descriptor *idesc;
if (arg->iface == NULL || arg->device == NULL)
return UMATCH_NONE;
idesc = usbd_get_interface_descriptor(arg->iface);
if (idesc == NULL) {
DPRINTF("%s: couldn't get idesc\n", __func__);
return UMATCH_NONE;
}
if (idesc->bInterfaceClass != UICLASS_AUDIO ||
idesc->bInterfaceSubClass != UISUBCLASS_AUDIOSTREAM)
return UMATCH_NONE;
return UMATCH_VENDOR_PRODUCT_CONF_IFACE;
}
void
uaudio_attach(struct device *parent, struct device *self, void *aux)
{
struct uaudio_softc *sc = (struct uaudio_softc *)self;
struct usb_attach_arg *arg = aux;
struct usb_config_descriptor *cdesc;
struct uaudio_blob desc;
cdesc = usbd_get_config_descriptor(arg->device);
if (cdesc == NULL)
return;
desc.rptr = (unsigned char *)cdesc;
desc.wptr = desc.rptr + UGETW(cdesc->wTotalLength);
sc->udev = arg->device;
sc->unit_list = NULL;
sc->names = NULL;
sc->alts = NULL;
sc->params_list = NULL;
sc->rclock = NULL;
sc->pclock = NULL;
sc->params = NULL;
sc->rate = 0;
sc->mode = 0;
sc->trigger_mode = 0;
sc->copy_todo = 0;
switch (sc->udev->speed) {
case USB_SPEED_LOW:
case USB_SPEED_FULL:
sc->ufps = 1000;
sc->sync_pktsz = 3;
sc->host_nframes = 50;
break;
case USB_SPEED_HIGH:
case USB_SPEED_SUPER:
sc->ufps = 8000;
sc->sync_pktsz = 4;
sc->host_nframes = 240;
break;
default:
printf("%s: unsupported bus speed\n", DEVNAME(sc));
return;
}
if (!uaudio_process_conf(sc, &desc))
return;
#ifdef UAUDIO_DEBUG
if (uaudio_debug)
uaudio_conf_print(sc);
#endif
uaudio_print(sc);
audio_attach_mi(&uaudio_hw_if, sc, arg->cookie, &sc->dev);
}
int
uaudio_detach(struct device *self, int flags)
{
struct uaudio_softc *sc = (struct uaudio_softc *)self;
struct uaudio_unit *unit;
struct uaudio_params *params;
struct uaudio_alt *alt;
struct uaudio_name *name;
struct uaudio_mixent *mixent;
int rv;
rv = config_detach_children(self, flags);
usbd_ref_wait(sc->udev);
while ((alt = sc->alts) != NULL) {
sc->alts = alt->next;
free(alt, M_USBDEV, sizeof(struct uaudio_alt));
}
while ((params = sc->params_list) != NULL) {
sc->params_list = params->next;
free(params, M_USBDEV, sizeof(struct uaudio_params));
}
while ((unit = sc->unit_list) != NULL) {
sc->unit_list = unit->unit_next;
while ((mixent = unit->mixent_list) != NULL) {
unit->mixent_list = mixent->next;
uaudio_ranges_clear(&mixent->ranges);
free(mixent, M_USBDEV, sizeof(struct uaudio_mixent));
}
uaudio_ranges_clear(&unit->rates);
free(unit, M_USBDEV, sizeof(struct uaudio_unit));
}
while ((name = sc->names)) {
sc->names = name->next;
free(name, M_USBDEV, sizeof(struct uaudio_name));
}
return rv;
}
int
uaudio_open(void *self, int flags)
{
struct uaudio_softc *sc = self;
struct uaudio_params *p;
if (usbd_is_dying(sc->udev))
return EIO;
usbd_ref_incr(sc->udev);
flags &= (FREAD | FWRITE);
for (p = sc->params_list; p != NULL; p = p->next) {
switch (flags) {
case FWRITE:
if (!p->palt)
break;
sc->mode = AUMODE_PLAY;
return 0;
case FREAD:
if (!p->ralt)
break;
sc->mode = AUMODE_RECORD;
return 0;
case FREAD | FWRITE:
if (!(p->ralt && p->palt))
break;
sc->mode = AUMODE_RECORD | AUMODE_PLAY;
return 0;
}
}
usbd_ref_decr(sc->udev);
return ENXIO;
}
void
uaudio_close(void *self)
{
struct uaudio_softc *sc = self;
sc->mode = 0;
usbd_ref_decr(sc->udev);
}
int
uaudio_set_params(void *self, int setmode, int usemode,
struct audio_params *ap, struct audio_params *ar)
{
struct uaudio_softc *sc = (struct uaudio_softc *)self;
struct uaudio_params *p, *best_mode, *best_rate, *best_nch;
int rate, rateindex;
#ifdef DIAGNOSTIC
if (setmode != usemode || setmode != sc->mode) {
printf("%s: bad call to uaudio_set_params()\n", DEVNAME(sc));
return EINVAL;
}
if (sc->mode == 0) {
printf("%s: uaudio_set_params(): not open\n", DEVNAME(sc));
return EINVAL;
}
#endif
rate = (sc->mode & AUMODE_PLAY) ? ap->sample_rate : ar->sample_rate;
rateindex = uaudio_rates_indexof(~0, rate);
DPRINTF("%s: rate %d -> %d (index %d)\n", __func__,
rate, uaudio_rates[rateindex], rateindex);
best_mode = best_rate = best_nch = NULL;
for (p = sc->params_list; p != NULL; p = p->next) {
if (sc->mode & AUMODE_PLAY) {
if (p->palt == NULL)
continue;
}
if (sc->mode & AUMODE_RECORD) {
if (p->ralt == NULL)
continue;
}
if (best_mode == NULL)
best_mode = p;
if ((uaudio_getrates(sc, setmode, p) & (1 << rateindex)) == 0)
continue;
if (best_rate == NULL)
best_rate = p;
if (sc->mode & AUMODE_PLAY) {
if (p->palt->nch != ap->channels)
continue;
}
if (sc->mode & AUMODE_RECORD) {
if (p->ralt->nch != ar->channels)
continue;
}
if (best_nch == NULL)
best_nch = p;
if (sc->mode & AUMODE_PLAY) {
if (p->palt->bits != ap->precision)
continue;
}
if (sc->mode & AUMODE_RECORD) {
if (p->ralt->bits != ar->precision)
continue;
}
break;
}
if (p == NULL) {
if (best_nch)
p = best_nch;
else if (best_rate)
p = best_rate;
else if (best_mode)
p = best_mode;
else
return ENOTTY;
}
rateindex = uaudio_rates_indexof(uaudio_getrates(sc, setmode, p), rate);
if (rateindex < 0)
return ENOTTY;
sc->params = p;
sc->rate = uaudio_rates[rateindex];
DPRINTF("%s: rate = %u\n", __func__, sc->rate);
if (sc->mode & AUMODE_PLAY) {
ap->sample_rate = sc->rate;
ap->precision = p->palt->bits;
ap->encoding = AUDIO_ENCODING_SLINEAR_LE;
ap->bps = p->palt->bps;
ap->msb = 1;
ap->channels = p->palt->nch;
}
if (sc->mode & AUMODE_RECORD) {
ar->sample_rate = sc->rate;
ar->precision = p->ralt->bits;
ar->encoding = AUDIO_ENCODING_SLINEAR_LE;
ar->bps = p->ralt->bps;
ar->msb = 1;
ar->channels = p->ralt->nch;
}
return 0;
}
unsigned int
uaudio_set_blksz(void *self, int mode,
struct audio_params *p, struct audio_params *r, unsigned int blksz)
{
struct uaudio_softc *sc = self;
unsigned int fps, fps_min;
unsigned int blksz_max, blksz_min;
fps_min = sc->ufps;
if (mode & AUMODE_PLAY) {
fps = sc->params->palt->fps;
if (fps_min > fps)
fps_min = fps;
}
if (mode & AUMODE_RECORD) {
fps = sc->params->ralt->fps;
if (fps_min > fps)
fps_min = fps;
}
blksz_min = (sc->rate * 2 + fps_min - 1) / fps_min;
blksz_max = sc->rate * (sc->host_nframes / UAUDIO_NXFERS_MIN) /
sc->ufps * 85 / 100;
if (blksz > blksz_max)
blksz = blksz_max;
else if (blksz < blksz_min)
blksz = blksz_min;
return blksz;
}
int
uaudio_trigger_output(void *self, void *start, void *end, int blksz,
void (*intr)(void *), void *arg, struct audio_params *param)
{
struct uaudio_softc *sc = self;
int err;
err = uaudio_stream_open(sc,
AUMODE_PLAY, start, end, blksz, intr, arg);
if (err)
return err;
sc->trigger_mode |= AUMODE_PLAY;
uaudio_trigger(sc);
return 0;
}
int
uaudio_trigger_input(void *self, void *start, void *end, int blksz,
void (*intr)(void *), void *arg, struct audio_params *param)
{
struct uaudio_softc *sc = self;
int err;
err = uaudio_stream_open(sc,
AUMODE_RECORD, start, end, blksz, intr, arg);
if (err)
return err;
sc->trigger_mode |= AUMODE_RECORD;
uaudio_trigger(sc);
return 0;
}
void
uaudio_copy_output(void *self, size_t todo)
{
struct uaudio_softc *sc = (struct uaudio_softc *)self;
int s;
s = splusb();
sc->copy_todo += todo;
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 3) {
printf("%s: copy_todo -> %zd (+%zd)\n", __func__,
sc->copy_todo, todo);
}
#endif
if (sc->mode == sc->trigger_mode)
uaudio_pdata_copy(sc);
splx(s);
}
void
uaudio_underrun(void *self)
{
struct uaudio_softc *sc = (struct uaudio_softc *)self;
struct uaudio_stream *s = &sc->pstream;
sc->copy_todo += s->ring_blksz;
#ifdef UAUDIO_DEBUG
if (uaudio_debug >= 3)
printf("%s: copy_todo -> %zd\n", __func__, sc->copy_todo);
#endif
uaudio_pdata_copy(sc);
}
int
uaudio_halt_output(void *self)
{
struct uaudio_softc *sc = (struct uaudio_softc *)self;
uaudio_stream_close(sc, AUMODE_PLAY);
sc->trigger_mode &= ~AUMODE_PLAY;
sc->copy_todo = 0;
return 0;
}
int
uaudio_halt_input(void *self)
{
struct uaudio_softc *sc = (struct uaudio_softc *)self;
uaudio_stream_close(sc, AUMODE_RECORD);
sc->trigger_mode &= ~AUMODE_RECORD;
return 0;
}
int
uaudio_get_port_do(struct uaudio_softc *sc, struct mixer_ctrl *ctl)
{
struct uaudio_unit *u;
struct uaudio_mixent *m;
unsigned char req_buf[4];
struct uaudio_blob p;
int i, nch, val, req_num;
if (!uaudio_mixer_byindex(sc, ctl->dev, &u, &m))
return ENOENT;
switch (sc->version) {
case UAUDIO_V1:
req_num = UAUDIO_V1_REQ_GET_CUR;
break;
case UAUDIO_V2:
req_num = UAUDIO_V2_REQ_CUR;
}
switch (m->type) {
case UAUDIO_MIX_SW:
p.rptr = p.wptr = req_buf;
if (!uaudio_req(sc,
UT_READ_CLASS_INTERFACE,
req_num,
m->req_sel,
m->chan < 0 ? 0 : m->chan,
sc->ctl_ifnum,
u->id,
req_buf,
1))
return EIO;
p.wptr++;
if (!uaudio_getnum(&p, 1, &val))
return EIO;
ctl->un.ord = !!val;
break;
case UAUDIO_MIX_NUM:
nch = uaudio_mixer_nchan(m, NULL);
ctl->un.value.num_channels = nch;
for (i = 0; i < nch; i++) {
p.rptr = p.wptr = req_buf;
if (!uaudio_req(sc,
UT_READ_CLASS_INTERFACE,
req_num,
m->req_sel,
m->chan < 0 ? 0 : i + 1,
sc->ctl_ifnum,
u->id,
req_buf,
2))
return EIO;
p.wptr += 2;
if (!uaudio_getnum(&p, 2, &val))
return EIO;
ctl->un.value.level[i] =
uaudio_ranges_decode(&m->ranges,
uaudio_sign_expand(val, 2));
m = m->next;
}
break;
case UAUDIO_MIX_ENUM:
break;
}
return 0;
}
int
uaudio_set_port_do(struct uaudio_softc *sc, struct mixer_ctrl *ctl)
{
struct uaudio_unit *u;
struct uaudio_mixent *m;
unsigned char req_buf[4];
unsigned int val;
int i, nch;
if (!uaudio_mixer_byindex(sc, ctl->dev, &u, &m))
return ENOENT;
switch (m->type) {
case UAUDIO_MIX_SW:
if (ctl->un.ord < 0 || ctl->un.ord > 1)
return EINVAL;
req_buf[0] = ctl->un.ord;
if (!uaudio_req(sc,
UT_WRITE_CLASS_INTERFACE,
UAUDIO_V1_REQ_SET_CUR,
m->req_sel,
m->chan < 0 ? 0 : m->chan,
sc->ctl_ifnum,
u->id,
req_buf,
1))
return EIO;
break;
case UAUDIO_MIX_NUM:
nch = uaudio_mixer_nchan(m, NULL);
ctl->un.value.num_channels = nch;
for (i = 0; i < nch; i++) {
val = uaudio_ranges_encode(&m->ranges,
ctl->un.value.level[i]);
DPRINTF("%s: ch %d, ctl %d, num val %d\n", __func__,
i, ctl->un.value.level[i], val);
req_buf[0] = val;
req_buf[1] = val >> 8;
if (!uaudio_req(sc,
UT_WRITE_CLASS_INTERFACE,
UAUDIO_V1_REQ_SET_CUR,
m->req_sel,
m->chan < 0 ? 0 : i + 1,
sc->ctl_ifnum,
u->id,
req_buf,
2))
return EIO;
m = m->next;
}
break;
case UAUDIO_MIX_ENUM:
break;
}
return 0;
}
int
uaudio_query_devinfo_do(struct uaudio_softc *sc, struct mixer_devinfo *devinfo)
{
struct uaudio_unit *u;
struct uaudio_mixent *m;
devinfo->next = -1;
devinfo->prev = -1;
switch (devinfo->index) {
case UAUDIO_CLASS_IN:
strlcpy(devinfo->label.name, AudioCinputs, MAX_AUDIO_DEV_LEN);
devinfo->type = AUDIO_MIXER_CLASS;
devinfo->mixer_class = -1;
return 0;
case UAUDIO_CLASS_OUT:
strlcpy(devinfo->label.name, AudioCoutputs, MAX_AUDIO_DEV_LEN);
devinfo->type = AUDIO_MIXER_CLASS;
devinfo->mixer_class = -1;
return 0;
}
if (!uaudio_mixer_byindex(sc, devinfo->index, &u, &m))
return ENOENT;
if (strcmp(m->fname, "level") == 0) {
strlcpy(devinfo->label.name, u->name, MAX_AUDIO_DEV_LEN);
} else {
if (m->chan == -1) {
snprintf(devinfo->label.name, MAX_AUDIO_DEV_LEN,
"%s_%s", u->name, m->fname);
} else {
snprintf(devinfo->label.name, MAX_AUDIO_DEV_LEN,
"%s_%s%u", u->name, m->fname, m->chan);
}
}
devinfo->mixer_class = u->mixer_class;
switch (m->type) {
case UAUDIO_MIX_SW:
devinfo->type = AUDIO_MIXER_ENUM;
devinfo->un.e.num_mem = 2;
devinfo->un.e.member[0].ord = 0;
strlcpy(devinfo->un.e.member[0].label.name, "off",
MAX_AUDIO_DEV_LEN);
devinfo->un.e.member[1].ord = 1;
strlcpy(devinfo->un.e.member[1].label.name, "on",
MAX_AUDIO_DEV_LEN);
break;
case UAUDIO_MIX_NUM:
devinfo->type = AUDIO_MIXER_VALUE;
devinfo->un.v.num_channels = uaudio_mixer_nchan(m, NULL);
devinfo->un.v.delta = 1;
break;
case UAUDIO_MIX_ENUM:
devinfo->type = AUDIO_MIXER_ENUM;
devinfo->un.e.num_mem = 0;
break;
}
return 0;
}
int
uaudio_get_port(void *arg, struct mixer_ctrl *ctl)
{
struct uaudio_softc *sc = arg;
int rc;
usbd_ref_incr(sc->udev);
rc = uaudio_get_port_do(sc, ctl);
usbd_ref_decr(sc->udev);
return rc;
}
int
uaudio_set_port(void *arg, struct mixer_ctrl *ctl)
{
struct uaudio_softc *sc = arg;
int rc;
usbd_ref_incr(sc->udev);
rc = uaudio_set_port_do(sc, ctl);
usbd_ref_decr(sc->udev);
return rc;
}
size_t
uaudio_display_name(void *arg, char *buf, size_t size)
{
struct uaudio_softc *sc = arg;
char *vendor = sc->udev->vendor;
char *product = sc->udev->product;
char *p = buf, *end = buf + size;
size_t i;
int c;
if (product == NULL || vendor == NULL)
return strlcpy(buf, DEVNAME(sc), size);
for (i = 0; product[i] != 0; i++) {
if (vendor[i] == 0) {
while (product[i] == ' ')
i++;
product += i;
break;
}
if (vendor[i] != product[i])
break;
}
while ((c = *product++) != 0) {
if (c < ' ' || c > '~')
continue;
if (p < end)
*p = c;
p++;
}
if (size > 0)
*(p < end ? p : end - 1) = 0;
if (sc->instnum > 0)
p += snprintf(p, p < end ? end - p : 0, "#%u", sc->instnum + 1);
return p - buf;
}
int
uaudio_query_devinfo(void *arg, struct mixer_devinfo *devinfo)
{
struct uaudio_softc *sc = arg;
int rc;
usbd_ref_incr(sc->udev);
rc = uaudio_query_devinfo_do(sc, devinfo);
usbd_ref_decr(sc->udev);
return rc;
}