/* * Purpose: USB audio streaming interface support */ /* * * This file is part of Open Sound System. * * Copyright (C) 4Front Technologies 1996-2008. * * This this source file is released under GPL v2 license (no other versions). * See the COPYING file included in the main directory of this source * distribution for the license terms and conditions. * */ #include "oss_config.h" #include "ossusb.h" #define SAMPLING_FREQ_CONTROL 0x01 #define PITCH_CONTROL 0x02 #define FORMAT_II_UNDEFINED 0x1000 #define FORMAT_II_MPEG 0x1001 #define FORMAT_II_AC3 0x1002 #define TMPBUF_SIZE 4096 #if 0 static int read_control_value (ossusb_devc * devc, int endpoint, int ctl, int l) { unsigned char buf[4]; int len, i, v; memset (buf, 0, sizeof (buf)); len = udi_usb_rcv_control_msg (devc->mixer_usbdev, 0, // endpoint GET_CUR, USB_RECIP_ENDPOINT | USB_TYPE_CLASS, // rqtype ctl << 8, // value endpoint, // index buf, // buffer l, // buflen OSS_HZ); if (len < 0) { cmn_err (CE_WARN, "Endpoint read error %d\n", len); return 0; } cmn_err (CE_CONT, "Read len %d (%d): ", len, l); for (i = 0; i < len; i++) cmn_err (CE_CONT, "%02x ", buf[i]); switch (len) { case 3: v = buf[0] | (buf[1] << 8) | (buf[2] << 16); break; default: cmn_err (CE_CONT, "oss usbaudio: Bad control read (%d)\n", l); } cmn_err (CE_CONT, "= %d\n", v); return v; } #endif static int write_control_value (ossusb_devc * devc, udi_endpoint_handle_t * endpoint, int ctl, int l, unsigned int v) { unsigned char buf[4]; int len; memset (buf, 0, sizeof (buf)); switch (l) { case 3: buf[0] = (v) & 0xff; buf[1] = (v >> 8) & 0xff; buf[2] = (v >> 16) & 0xff; break; default: cmn_err (CE_CONT, "oss usbaudio: Bad control size %d\n", l); return OSS_EIO; } len = udi_usb_snd_control_msg (devc->mixer_usbdev, 0, // endpoint SET_CUR, USB_RECIP_ENDPOINT | USB_TYPE_CLASS, // rqtype ctl << 8, // value udi_endpoint_get_num (endpoint), // index buf, // buffer l, // buflen OSS_HZ); if (len < 0) { cmn_err (CE_WARN, "Endpoint control write error %d\n", len); return OSS_EIO; } return len; } static void set_fraglimits (adev_t * adev, ossusb_portc * portc) { int l, m; if (portc->bytes_per_sample < 1) portc->bytes_per_sample = 1; l = portc->bytes_per_sample * adev->min_channels * portc->speed / 1000; l = (l / portc->bytes_per_sample) * portc->bytes_per_sample; m = 2; while (m < l) m *= 2; adev->min_block = m; adev->max_block = TMPBUF_SIZE / 2; portc->fragment_size = l; } static int usbaudio_set_rate (int dev, int arg) { adev_p adev = audio_engines[dev]; ossusb_portc *portc = adev->portc; ossusb_devc *devc = adev->devc; int i, x, diff, bestdiff; if (devc->disabled) return OSS_EPIPE; if (arg == 0) return portc->speed; if (arg < adev->min_rate) arg = adev->min_rate; if (arg > adev->max_rate) arg = adev->max_rate; if (!(adev->caps & PCM_CAP_FREERATE)) { /* Search for the nearest supported rate */ bestdiff = 0x7fffffff; x = -1; for (i = 0; i < adev->nrates; i++) { diff = arg - adev->rates[i]; if (diff < 0) diff = -diff; /* ABS */ if (diff < bestdiff) { x = i; bestdiff = diff; } } if (x > -1) arg = adev->rates[x]; } portc->speed = arg; set_fraglimits (adev, portc); return portc->speed; } /*ARGSUSED*/ static short usbaudio_set_channels (int dev, short arg) { adev_p adev = audio_engines[dev]; ossusb_devc *devc = adev->devc; if (devc->disabled) return OSS_EPIPE; return adev->min_channels; /* max_channels should be the same too */ } /*ARGSUSED*/ static unsigned int usbaudio_set_format (int dev, unsigned int arg) { adev_p adev = audio_engines[dev]; ossusb_devc *devc = adev->devc; if (devc->disabled) return adev->oformat_mask; return adev->oformat_mask; /* iformat_mask should be the same too */ } /*ARGSUSED*/ static int usbaudio_ioctl (int dev, unsigned int cmd, ioctl_arg arg) { ossusb_devc *devc = audio_engines[dev]->devc; if (devc->disabled) return OSS_EPIPE; return OSS_EINVAL; } /*ARGSUSED*/ static int setup_format_I (ossusb_devc * devc, ossusb_portc * portc, adev_p adev, unsigned char *d, int l) { int min_rate = 0, max_rate = 0; int i, n; int frame_size, bits, channels; unsigned int fmt = 0; if (usb_trace > 1) { cmn_err (CE_CONT, "AS_FORMAT_TYPE: FORMAT_TYPE_I, #ch %d, framesize %d, bits %d, freq_type %02x, freq=", d[4], d[5], d[6], d[7]); if (d[7] == 0) cmn_err (CE_CONT, "%d-%d ", ossusb_get_int (&d[8], 3), ossusb_get_int (&d[11], 3)); { for (i = 0; i < d[7]; i++) cmn_err (CE_CONT, "%d ", ossusb_get_int (&d[8 + i * 3], 3)); cmn_err (CE_CONT, " "); } switch (d[3]) { case 0x00: cmn_err (CE_CONT, "Undefined "); break; case 0x01: cmn_err (CE_CONT, "PCM "); break; case 0x02: cmn_err (CE_CONT, "PCM8 "); break; case 0x03: cmn_err (CE_CONT, "float "); break; case 0x04: cmn_err (CE_CONT, "A-Law "); break; case 0x05: cmn_err (CE_CONT, "u-Law "); break; } cmn_err (CE_CONT, "\n"); } channels = d[4]; frame_size = d[5]; bits = d[6]; UDB (cmn_err (CE_CONT, "Channels %d, bits %d (%d bytes)\n", channels, bits, frame_size)); adev->min_channels = adev->max_channels = channels; portc->convert_3byte = 0; switch (d[3]) { case 0x01: fmt = AFMT_S16_LE; break; case 0x02: fmt = AFMT_U8; break; case 0x03: fmt = AFMT_FLOAT; break; case 0x04: fmt = AFMT_A_LAW; break; case 0x05: fmt = AFMT_MU_LAW; break; } if (fmt == AFMT_S16_LE) /* Have to check the frame size too */ { switch (frame_size) { case 1: fmt = AFMT_S8; break; case 2: fmt = AFMT_S16_LE; break; case 3: fmt = AFMT_S32_LE; frame_size = 4; portc->convert_3byte = 1; break; case 4: fmt = AFMT_S32_LE; break; } } portc->bytes_per_sample = frame_size; adev->oformat_mask = adev->iformat_mask = fmt; UDB (cmn_err (CE_CONT, "Format mask %08x\n", fmt)); adev->caps &= ~PCM_CAP_FREERATE; n = d[7]; if (n < 1) /* Free rate selection between min (0) and max (1) */ { n = 2; adev->caps |= PCM_CAP_FREERATE; } min_rate = 0x7fffffff; max_rate = 0; if (n > 20) { cmn_err (CE_WARN, "The device supports too many sample rates\n"); n = 20; } adev->nrates = 0; for (i = 0; i < n; i++) { int rate = ossusb_get_int (&d[8 + i * 3], 3); #if 0 /* Skip rates that are not multiples of 1000 Hz */ if (rate % 1000) continue; #endif if (rate < min_rate) min_rate = rate; if (rate > max_rate) max_rate = rate; adev->rates[adev->nrates++] = rate; } adev->min_rate = min_rate; adev->max_rate = max_rate; UDB (cmn_err (CE_CONT, "Min rate %d, max rate %d\n", min_rate, max_rate)); adev->caps &= ~DSP_CH_MASK; switch (channels) { case 1: adev->caps |= DSP_CH_MONO; break; case 2: adev->caps |= DSP_CH_STEREO; break; default: adev->caps |= DSP_CH_MULTI; break; } return 0; } /*ARGSUSED*/ static int setup_format_II (ossusb_devc * devc, ossusb_portc * portc, adev_p adev, unsigned char *d, int l) { int min_rate = 0, max_rate = 0; int i; if (usb_trace > 1) { cmn_err (CE_CONT, "MaxBitRate %d ", d[4]); cmn_err (CE_CONT, "SamplesPerFrame %d ", d[5]); if (d[8] == 0) cmn_err (CE_CONT, "Sample Rates %d-%d ", ossusb_get_int (&d[9], 3), ossusb_get_int (&d[12], 3)); else { int n; int min_rate = 0x7fffffff; int max_rate = 0; n = d[8]; if (n > 20) { cmn_err (CE_CONT, "oss usbaudio: Too many sample rates (%d)\n", n); n = 20; } adev->nrates = 0; cmn_err (CE_CONT, "Possible sample rates: "); for (i = 0; i < d[8]; i++) { int rate = ossusb_get_int (&d[9 + i * 3], 3); #if 0 /* Skip rates that are not multiples of 1000 Hz */ if (rate % 1000) continue; #endif if (rate < min_rate) min_rate = rate; if (rate > max_rate) max_rate = rate; cmn_err (CE_CONT, "%d ", rate); adev->rates[adev->nrates++] = rate; } adev->min_rate = min_rate; adev->max_rate = max_rate; } cmn_err (CE_CONT, "\n"); } adev->caps &= ~PCM_CAP_FREERATE; if (d[8] == 0) { min_rate = ossusb_get_int (&d[9], 3); max_rate = ossusb_get_int (&d[12], 3); adev->caps |= PCM_CAP_FREERATE; } else { min_rate = 1 << 30; max_rate = 0; for (i = 0; i < d[8]; i++) { int r = ossusb_get_int (&d[9 + i * 3], 3); if (r < min_rate) min_rate = r; if (r > max_rate) max_rate = r; } } adev->min_channels = adev->max_channels = 2; adev->oformat_mask = adev->iformat_mask = AFMT_AC3; adev->min_rate = min_rate; adev->max_rate = max_rate; return 0; } /*ARGSUSED*/ static int setup_format_specific (ossusb_devc * devc, ossusb_portc * portc, adev_p adev, unsigned char *d, int l) { int fmt; fmt = ossusb_get_int (&d[3], 2); if (usb_trace > 1) cmn_err (CE_CONT, "Format specific: fmt=%04x\n", fmt); switch (fmt) { case FORMAT_II_MPEG: if (usb_trace > 1) cmn_err (CE_CONT, "MPEG format\n"); adev->oformat_mask = adev->iformat_mask = AFMT_MPEG; break; case FORMAT_II_AC3: if (usb_trace > 1) cmn_err (CE_CONT, "AC3 format\n"); adev->oformat_mask = adev->iformat_mask = AFMT_AC3; #if 0 cmn_err (CE_CONT, "BSID=%08x\n", ossusb_get_int (&d[5], 4)); cmn_err (CE_CONT, "AC3Features %02x\n", d[9]); #endif break; default: cmn_err (CE_CONT, "oss usbaudio: Unsupported FORMAT II tag %04x\n", fmt); adev->enabled = 0; return OSS_ENXIO; } return 0; } static int prepare_altsetting (ossusb_devc * devc, ossusb_portc * portc, int new_setting) { int desc_len; unsigned char *desc, *d; int l, p; int err; adev_p adev; adev = audio_engines[portc->audio_dev]; adev->enabled = 1; portc->disabled = 0; if (portc->act_setting == new_setting) /* No need to change */ return 0; if (new_setting < portc->num_settings) desc = udi_usbdev_get_altsetting (portc->usbdev, new_setting, &desc_len); else desc = NULL; if (desc == NULL || desc_len < 3) { cmn_err (CE_CONT, "Audio device %d not available when altsetting=%d\n", audio_engines[portc->audio_dev]->real_dev, new_setting); portc->disabled = 1; adev->enabled = 0; portc->act_setting = new_setting; return OSS_ENXIO; } UDB (cmn_err (CE_CONT, "Select active setting %d on interface %d (dsp%d)\n", new_setting, portc->if_number, adev->engine_num)); portc->act_setting = new_setting; p = 0; while (p < desc_len) { int i; d = desc + p; l = *d; if (usb_trace > 1) { cmn_err (CE_CONT, "Streaming desc: "); for (i = 0; i < l; i++) cmn_err (CE_CONT, "%02x ", d[i]); cmn_err (CE_CONT, "\n"); } if (d[1] != CS_INTERFACE) { UDB (cmn_err (CE_CONT, "Unknown descriptor type %02x\n", d[1]))} else switch (d[2]) { case AS_GENERAL: portc->terminal_link = d[3]; portc->pipeline_delay = d[4]; if (usb_trace > 1) { cmn_err (CE_CONT, "AS_GENERAL "); cmn_err (CE_CONT, "Terminal link %d/%s ", d[3], devc->units[d[3]].name); cmn_err (CE_CONT, "Delay %d ", d[4]); cmn_err (CE_CONT, "Format tag %02x%02x ", d[5], d[6]); cmn_err (CE_CONT, "\n"); } break; case AS_FORMAT_TYPE: if (usb_trace > 1) { cmn_err (CE_CONT, "AS_FORMAT_TYPE: FORMAT_TYPE_%d: ", d[3]); } switch (d[3]) { case 1: /* FORMAT_TYPE_I */ if ((err = setup_format_I (devc, portc, adev, d, l)) < 0) return err; break; case 2: /* FORMAT_TYPE_II */ if ((err = setup_format_II (devc, portc, adev, d, l)) < 0) return err; break; default: cmn_err (CE_CONT, "\noss usbaudio: Unsupported format type %d\n", d[3]); adev->enabled = 0; return OSS_ENXIO; } break; case AS_FORMAT_SPECIFIC: if ((err = setup_format_specific (devc, portc, adev, d, l)) < 0) return err; break; default: UDB (cmn_err (CE_CONT, "Unknown descriptor subtype %02x\n", d[2])); } p += l; } desc = udi_usbdev_get_endpoint (portc->usbdev, portc->act_setting, 0, &l); if (desc == NULL) { cmn_err (CE_CONT, "oss usbaudio: Bad endpoint\n"); return OSS_EIO; } portc->endpoint_desc = desc; desc = udi_usbdev_get_endpoint (portc->usbdev, portc->act_setting, 1, &l); #if 0 if (desc != NULL) { /* TODO: Handle sync endpoints */ cmn_err (CE_CONT, "Sync Endpoint: "); ossusb_dump_desc (desc, l); } #endif return 0; } static void usbaudio_close (int dev, int mode); /*ARGSUSED*/ static int usbaudio_open (int dev, int mode, int open_flags) { ossusb_devc *devc = audio_engines[dev]->devc; ossusb_portc *portc = audio_engines[dev]->portc; oss_native_word flags, phaddr; int err; int i; if (devc->disabled) return OSS_EPIPE; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->open_mode != 0) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return OSS_EBUSY; } portc->open_mode = mode; MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); if ((err = prepare_altsetting (devc, portc, portc->act_setting)) < 0) { portc->open_mode = 0; return err; } { int i; for (i = 0; i < 2; i++) portc->tmp_buf[i] = CONTIG_MALLOC (devc->osdev, TMPBUF_SIZE, MEMLIMIT_32BITS, &phaddr, portc->tmpbuf_dma_handle[i]); } if ((err = udi_usbdev_set_interface (portc->usbdev, portc->if_number, portc->act_setting)) < 0) { cmn_err (CE_NOTE, "oss usbaudio: Failed to set interface mode, error %d - ignored\n", err); // portc->open_mode = 0; //return err; } portc->curr_datapipe = 0; if ((portc->endpoint_handle = udi_open_endpoint (portc->usbdev, portc->endpoint_desc)) == NULL) { usbaudio_close (dev, mode); cmn_err (CE_WARN, "Cannot open audio pipe\n"); return OSS_ENOMEM; } for (i = 0; i < 2; i++) { if ((portc->datapipe[i] = udi_usb_alloc_request (portc->usbdev, portc->endpoint_handle, 1, UDI_USBXFER_ISO_WRITE)) == NULL) { usbaudio_close (dev, mode); cmn_err (CE_WARN, "Cannot alloc isoc request\n"); return OSS_ENOMEM; } } set_fraglimits (audio_engines[dev], portc); return 0; } static void usbaudio_reset (int dev); /*ARGSUSED*/ static void usbaudio_close (int dev, int mode) { ossusb_devc *devc = audio_engines[dev]->devc; ossusb_portc *portc = audio_engines[dev]->portc; int i; usbaudio_reset (dev); for (i = 0; i < 2; i++) { if (portc->datapipe[i] != NULL) { udi_usb_free_request (portc->datapipe[i]); portc->datapipe[i] = NULL; } if (portc->tmp_buf[i] != NULL) { CONTIG_FREE (devc->osdev, portc->tmp_buf[i], TMPBUF_SIZE, portc->tmpbuf_dma_handle[i]); portc->tmp_buf[i] = NULL; } } if (portc->endpoint_handle != NULL) udi_close_endpoint (portc->endpoint_handle); udi_usbdev_set_interface (portc->usbdev, portc->if_number, 0); portc->open_mode = 0; } /*ARGSUSED*/ static void usbaudio_output_block (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { ossusb_devc *devc = audio_engines[dev]->devc; if (devc->disabled) return; } /*ARGSUSED*/ static void usbaudio_start_input (int dev, oss_native_word buf, int count, int fragsize, int intrflag) { ossusb_devc *devc = audio_engines[dev]->devc; if (devc->disabled) return; } static int feed_output (int dev, ossusb_devc * devc, ossusb_portc * portc); static void start_input (int dev, ossusb_devc * devc, ossusb_portc * portc); static int copy_input (ossusb_portc * portc, dmap_t * dmap, unsigned char *buf, int len) { int outlen = 0; int offs; offs = (int) (dmap->byte_counter % dmap->bytes_in_use); while (len > 0) { int l; l = len; if (portc->convert_3byte) { int i, n; int *dmabuf; l = (l * 4) / 3; /* Check for buffer wraparound */ if (offs + l > dmap->bytes_in_use) l = dmap->bytes_in_use - offs; n = (l / 4); if (dmap == NULL || dmap->dmabuf == NULL) return outlen; dmabuf = (int *) (dmap->dmabuf + offs); for (i = 0; i < n; i++) { int v; v = buf[2] | (buf[1] << 8) | (buf[0] << 16); *dmabuf++ = v; buf += 3; } outlen += l; } else { unsigned char *dmabuf; dmabuf = dmap->dmabuf; if (dmap == NULL || dmap->dmabuf == NULL) return outlen; /* Check for buffer wraparound */ if (offs + l > dmap->bytes_in_use) l = dmap->bytes_in_use - offs; memcpy (dmabuf + offs, buf, l); outlen += l; buf += l; } len -= l; offs = 0; } return outlen; } /*ARGSUSED*/ static void play_callback (udi_usb_request_t * request, void *arg) { ossusb_portc *portc = arg; feed_output (portc->audio_dev, portc->devc, portc); oss_audio_outputintr (portc->audio_dev, AINTR_NO_POINTER_UPDATES | AINTR_LOCALQUEUE); } static void rec_callback (udi_usb_request_t * request, void *arg) { ossusb_portc *portc = arg; dmap_t *dmap; int len; if (portc == NULL || (unsigned long) arg < 4096) { cmn_err (CE_WARN, "Bad portc\n"); return; } dmap = audio_engines[portc->audio_dev]->dmap_in; len = udi_usb_request_actlen (request); if (len == 0) return; /* No data so it looks like we are closing down */ if ((len = copy_input (portc, dmap, udi_usb_request_actdata (request), len)) < 1) { cmn_err (CE_WARN, "Saving recorded data failed (%d)\n", len); return; } oss_audio_inc_byte_counter (dmap, len); oss_audio_inputintr (portc->audio_dev, AINTR_NO_POINTER_UPDATES); #ifdef linux start_input (portc->audio_dev, portc->devc, portc); #endif } #if 0 /* * Testing stuff only */ static int sin_gen (void) { static int phase = 0, v; static short sinebuf[48] = { 0, 4276, 8480, 12539, 16383, 19947, 23169, 25995, 28377, 30272, 31650, 32486, 32767, 32486, 31650, 30272, 28377, 25995, 23169, 19947, 16383, 12539, 8480, 4276, 0, -4276, -8480, -12539, -16383, -19947, -23169, -25995, -28377, -30272, -31650, -32486, -32767, -32486, -31650, -30272, -28377, -25995, -23169, -19947, -16383, -12539, -8480, -4276 }; v = sinebuf[phase] * 256; phase = (phase + 1) % 48; return v; } #endif /*ARGSUSED*/ static int output_convert (ossusb_devc * devc, ossusb_portc * portc, dmap_p dmap, unsigned char *dmabuf, int pos, int pn, int total_len) { unsigned char *tmpbuf = portc->tmp_buf[pn], *b; int i, n, len, out_size = 0; int err; while (total_len > 0) { int l = total_len; /* Check for buffer wraparound */ if (pos + l > dmap->bytes_in_use) l = dmap->bytes_in_use - pos; total_len -= l; b = dmabuf + pos; if (portc->convert_3byte) { int *buf; n = l / sizeof (*buf); buf = (int *) b; len = n * 3; for (i = 0; i < n; i++) { int val = (*buf++); val /= 256; // val=sin_gen(); *tmpbuf++ = (val) & 0xff; *tmpbuf++ = (val >> 8) & 0xff; *tmpbuf++ = (val >> 16) & 0xff; } } else { len = l; memcpy (tmpbuf, b, l); tmpbuf += l; } pos = 0; out_size += len; } if ((err = udi_usb_submit_request (portc->datapipe[pn], play_callback, portc, portc->endpoint_handle, UDI_USBXFER_ISO_WRITE, portc->tmp_buf[pn], out_size)) < 0) { //cmn_err(CE_CONT, "oss usbaudio: Write transfer eror %d\n", err); return err; } return 0; } static int feed_output (int dev, ossusb_devc * devc, ossusb_portc * portc) { int pn, pos, len; oss_native_word flags; adev_p adev = audio_engines[dev]; dmap_p dmap = adev->dmap_out; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); if (portc->stopping) { MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } pn = portc->curr_datapipe; portc->curr_datapipe = (portc->curr_datapipe + 1) % NR_DATAPIPES; pos = (int) (dmap->byte_counter % dmap->bytes_in_use); len = portc->fragment_size; portc->overflow_samples += portc->overflow_size; if (portc->overflow_samples > 1000) { len += dmap->frame_size * (portc->overflow_samples / 1000); portc->overflow_samples = portc->overflow_samples % 1000; } output_convert (devc, portc, dmap, dmap->dmabuf, pos, pn, len); oss_audio_inc_byte_counter (dmap, len); MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); return 0; } static void start_input (int dev, ossusb_devc * devc, ossusb_portc * portc) { int frag, err; oss_native_word flags; adev_p adev = audio_engines[dev]; dmap_p dmap = adev->dmap_in; if (portc->stopping) return; MUTEX_ENTER_IRQDISABLE (devc->mutex, flags); frag = 0; if ((err = udi_usb_submit_request (portc->datapipe[0], rec_callback, portc, portc->endpoint_handle, UDI_USBXFER_ISO_READ, dmap->dmabuf + frag * portc->fragment_size, portc->fragment_size)) < 0) { cmn_err (CE_WARN, "oss usbaudio: Read transfer error %d\n", err); cmn_err (CE_CONT, "Endpoint %02x\n", udi_endpoint_get_num (portc->endpoint_handle)); } MUTEX_EXIT_IRQRESTORE (devc->mutex, flags); } static void usbaudio_trigger (int dev, int state) { ossusb_devc *devc = audio_engines[dev]->devc; ossusb_portc *portc = audio_engines[dev]->portc; if (devc->disabled) return; if (portc->open_mode & PCM_ENABLE_OUTPUT) { if ((portc->prepared_modes & PCM_ENABLE_OUTPUT) && (state & PCM_ENABLE_OUTPUT)) { portc->prepared_modes &= ~PCM_ENABLE_OUTPUT; portc->curr_datapipe = 0; portc->stopping = 0; feed_output (dev, devc, portc); feed_output (dev, devc, portc); } else if (!(state & PCM_ENABLE_OUTPUT)) { portc->stopping = 1; #if 1 udi_usb_cancel_request (portc->datapipe[0]); udi_usb_cancel_request (portc->datapipe[1]); #endif portc->curr_datapipe = 0; } } if (portc->open_mode & PCM_ENABLE_INPUT) { if ((portc->prepared_modes & PCM_ENABLE_INPUT) && (state & PCM_ENABLE_INPUT)) { portc->prepared_modes &= ~PCM_ENABLE_INPUT; portc->stopping = 0; start_input (dev, devc, portc); } else if (!(state & PCM_ENABLE_INPUT)) { portc->stopping = 1; #if 0 udi_usb_cancel_request (portc->datapipe[0]); udi_usb_cancel_request (portc->datapipe[1]); #endif portc->curr_datapipe = 0; } } } static void usbaudio_reset (int dev) { usbaudio_trigger (dev, 0); } /*ARGSUSED*/ static int usbaudio_prepare_for_input (int dev, int bsize, int bcount) { ossusb_devc *devc = audio_engines[dev]->devc; ossusb_portc *portc = audio_engines[dev]->portc; adev_p adev = audio_engines[dev]; if (devc->disabled) return OSS_EPIPE; if (adev->flags & ADEV_NOINPUT) return OSS_ENOTSUP; portc->stopping = 0; if (write_control_value (devc, portc->endpoint_handle, SAMPLING_FREQ_CONTROL, 3, portc->speed) < 0) { cmn_err (CE_CONT, "Failed to set %d Hz sampling rate\n", portc->speed); return OSS_EIO; } /* * Handle fractional samples that don't fit in the 1ms period. */ portc->overflow_size = portc->speed % 1000; portc->overflow_samples = 0; portc->prepared_modes |= PCM_ENABLE_INPUT; return 0; } /*ARGSUSED*/ static int usbaudio_prepare_for_output (int dev, int bsize, int bcount) { ossusb_devc *devc = audio_engines[dev]->devc; ossusb_portc *portc = audio_engines[dev]->portc; adev_p adev = audio_engines[dev]; if (devc->disabled) return OSS_EPIPE; if (adev->flags & ADEV_NOOUTPUT) return OSS_ENOTSUP; portc->stopping = 0; if (write_control_value (devc, portc->endpoint_handle, SAMPLING_FREQ_CONTROL, 3, portc->speed) < 0) { cmn_err (CE_CONT, "Failed to set %d Hz sampling rate\n", portc->speed); return OSS_EIO; } /* * Handle fractional samples that don't fit in the 1ms period. */ portc->overflow_size = portc->speed % 1000; portc->overflow_samples = 0; portc->prepared_modes |= PCM_ENABLE_OUTPUT; return 0; } static int usbaudio_check_input (int dev) { ossusb_devc *devc = audio_engines[dev]->devc; if (devc->disabled) { cmn_err (CE_CONT, "oss usbaudio: Audio device %d removed from the system.\n", dev); return OSS_EPIPE; } cmn_err (CE_CONT, "oss usbaudio: Audio input timed out on device %d.\n", dev); return OSS_EIO; } static int usbaudio_check_output (int dev) { ossusb_devc *devc = audio_engines[dev]->devc; if (devc->disabled) { cmn_err (CE_CONT, "oss usbaudio: Audio device %d removed from the system.\n", dev); return OSS_EPIPE; } cmn_err (CE_CONT, "oss usbaudio: Audio output timed out on device %d.\n", dev); return OSS_EIO; } static int usbaudio_local_qlen (int dev) { ossusb_portc *portc = audio_engines[dev]->portc; dmap_p dmap = audio_engines[dev]->dmap_out; int delay = portc->pipeline_delay + 1; /* Pipeline delay in 1 msec ticks */ delay = delay * dmap->data_rate / 1000; /* Bytes/msec */ return delay; } static audiodrv_t usbaudio_driver = { usbaudio_open, usbaudio_close, usbaudio_output_block, usbaudio_start_input, usbaudio_ioctl, usbaudio_prepare_for_input, usbaudio_prepare_for_output, usbaudio_reset, usbaudio_local_qlen, NULL, NULL, NULL, usbaudio_trigger, usbaudio_set_rate, usbaudio_set_format, usbaudio_set_channels, NULL, NULL, usbaudio_check_input, usbaudio_check_output, NULL, /* usbaudio_alloc_buffer */ NULL, /* usbaudio_free_buffer */ NULL, NULL, NULL /* usbaudio_get_buffer_pointer */ }; ossusb_devc * ossusb_init_audiostream (ossusb_devc * devc, udi_usb_devc * usbdev, int inum, int reinit) { int nsettings, actsetting = 0, desc_len; unsigned char *desc, *d; adev_p adev; int i, p, l; int portc_num; void *endpoint_desc; char dev_name[128]; int opts = ADEV_AUTOMODE; ossusb_portc *portc; if (devc->num_audio_engines >= MAX_PORTC) { cmn_err (CE_CONT, "usbaudio: Too many audio streaming interfaces\n"); return devc; } if (usbdev == NULL) { cmn_err (CE_CONT, "usbaudio: usbdev==NULL\n"); return devc; } nsettings = udi_usbdev_get_num_altsettings (usbdev); desc = udi_usbdev_get_endpoint (usbdev, 1, 0, &l); if (desc != NULL) { /* cmn_err(CE_CONT, "Endpoint: ");ossusb_dump_desc(desc, l); */ endpoint_desc = desc; } else endpoint_desc = NULL; if (reinit) for (i = 0; i < devc->num_audio_engines; i++) if (devc->portc[i].orig_endpoint_desc == endpoint_desc) /* Already registered this */ { prepare_altsetting (devc, &devc->portc[i], devc->portc[i].act_setting); return devc; } portc = &devc->portc[devc->num_audio_engines]; portc_num = devc->num_audio_engines; portc->if_number = inum; portc->endpoint_desc = portc->orig_endpoint_desc = endpoint_desc; portc->usbdev = usbdev; portc->act_setting = -1; /* Set to an impossible value */ devc->num_audio_engines++; memset (dev_name, 0, sizeof (dev_name)); strncpy (dev_name, devc->dev_name, sizeof (dev_name) - 1); portc->num_settings = nsettings; #if 1 if (usb_trace > 2) for (i = 0; i < nsettings; i++) { desc = udi_usbdev_get_altsetting (usbdev, i, &desc_len); cmn_err (CE_CONT, "\nDumping altsetting %d (l=%d)\n", i, desc_len); if (usb_trace) ossusb_dump_desc (desc, desc_len); } #endif desc = udi_usbdev_get_altsetting (usbdev, actsetting, &desc_len); if (desc == NULL || desc_len < 1) for (i = 0; i < nsettings && (desc == NULL || desc_len < 1); i++) { UDB (cmn_err (CE_CONT, "Trying to read altsetting %d\n", i)); desc = udi_usbdev_get_altsetting (usbdev, i, &desc_len); if (desc != NULL) actsetting = i; } UDB (cmn_err (CE_CONT, "Altsetting %d, len %d\n", actsetting, desc_len)); if (desc == NULL) { cmn_err (CE_WARN, "Can't read interface descriptors\n"); return NULL; } if (usb_trace > 2) ossusb_dump_desc (desc, desc_len); p = 0; while (p < desc_len) { d = desc + p; l = *d; if (d[1] != CS_INTERFACE) { UDB (cmn_err (CE_CONT, "Unknown descriptor type %02x\n", d[1]))} else switch (d[2]) { case AS_GENERAL: portc->terminal_link = d[3]; break; } p += l; } if (portc->terminal_link > 0 && portc->terminal_link <= devc->nunits) { char *s = dev_name + strlen (dev_name); sprintf (s, " %s", devc->units[portc->terminal_link].name); s += strlen (s); if (devc->units[portc->terminal_link].typ == TY_OUTPUT) /* USB terminal type */ { opts |= ADEV_NOOUTPUT; } else { opts |= ADEV_NOINPUT; } } if ((portc->audio_dev = oss_install_audiodev (OSS_AUDIO_DRIVER_VERSION, devc->osdev, devc->osdev, dev_name, &usbaudio_driver, sizeof (audiodrv_t), opts, AFMT_S16_NE, devc, -1)) < 0) { return devc; } portc->devc = devc; adev = audio_engines[portc->audio_dev]; adev->portc = portc; adev->mixer_dev = devc->mixer_dev; adev->rate_source = devc->portc[0].audio_dev; adev->max_block = 256; adev->min_fragments = 4; /* vmix needs this */ prepare_altsetting (devc, portc, 1); if (portc->num_settings > 2) { char name[128]; sprintf (name, "%s-altset", devc->units[portc->terminal_link].name); ossusb_create_altset_control (devc->mixer_dev, portc_num, portc->num_settings, name); } #if 0 // TODO: This needs to be checked before vmix is enabled #ifdef CONFIG_OSS_VMIX if (devc->units[portc->terminal_link].typ != TY_OUTPUT) vmix_attach_audiodev(devc->osdev, portc->audio_dev, -1, 0); #endif #endif return devc; } /*ARGSUSED*/ int ossusb_change_altsetting (int dev, int ctrl, unsigned int cmd, int value) { ossusb_devc *devc = mixer_devs[dev]->devc; ossusb_portc *portc; if (ctrl < 0 || ctrl >= devc->num_audio_engines) return OSS_ENXIO; portc = &devc->portc[ctrl]; if (cmd == SNDCTL_MIX_READ) return portc->act_setting; if (value >= portc->num_settings) value = portc->num_settings - 1; if (portc->act_setting != value) { prepare_altsetting (devc, portc, value); } return value; }