/* Stream disable request timeout during USB device disconnect */ #define DEV_RELEASE_WAIT_TIMEOUT 10000 /* in ms */
/* Data interval calculation parameters */ #define BUS_INTERVAL_FULL_SPEED 1000 /* in us */ #define BUS_INTERVAL_HIGHSPEED_AND_ABOVE 125 /* in us */ #define MAX_BINTERVAL_ISOC_EP 16
staticint info_idx_from_ifnum(int card_num, int intf_num, bool enable)
{ int i;
/* * default index 0 is used when info is allocated upon * first enable audio stream req for a pcm device
*/ if (enable && !uadev[card_num].info) return 0;
for (i = 0; i < uadev[card_num].num_intf; i++) { if (enable && !uadev[card_num].info[i].in_use) return i; elseif (!enable &&
uadev[card_num].info[i].intf_num == intf_num) return i;
}
/* check if another bit is set then bail out */
bus_intval_mult = bus_intval_mult >> binterval; if (bus_intval_mult) return -EINVAL;
return (binterval - 1);
}
/* maps audio format received over QMI to asound.h based pcm format */ static snd_pcm_format_t map_pcm_format(enum usb_qmi_audio_format fmt_received)
{ switch (fmt_received) { case USB_QMI_PCM_FORMAT_S8: return SNDRV_PCM_FORMAT_S8; case USB_QMI_PCM_FORMAT_U8: return SNDRV_PCM_FORMAT_U8; case USB_QMI_PCM_FORMAT_S16_LE: return SNDRV_PCM_FORMAT_S16_LE; case USB_QMI_PCM_FORMAT_S16_BE: return SNDRV_PCM_FORMAT_S16_BE; case USB_QMI_PCM_FORMAT_U16_LE: return SNDRV_PCM_FORMAT_U16_LE; case USB_QMI_PCM_FORMAT_U16_BE: return SNDRV_PCM_FORMAT_U16_BE; case USB_QMI_PCM_FORMAT_S24_LE: return SNDRV_PCM_FORMAT_S24_LE; case USB_QMI_PCM_FORMAT_S24_BE: return SNDRV_PCM_FORMAT_S24_BE; case USB_QMI_PCM_FORMAT_U24_LE: return SNDRV_PCM_FORMAT_U24_LE; case USB_QMI_PCM_FORMAT_U24_BE: return SNDRV_PCM_FORMAT_U24_BE; case USB_QMI_PCM_FORMAT_S24_3LE: return SNDRV_PCM_FORMAT_S24_3LE; case USB_QMI_PCM_FORMAT_S24_3BE: return SNDRV_PCM_FORMAT_S24_3BE; case USB_QMI_PCM_FORMAT_U24_3LE: return SNDRV_PCM_FORMAT_U24_3LE; case USB_QMI_PCM_FORMAT_U24_3BE: return SNDRV_PCM_FORMAT_U24_3BE; case USB_QMI_PCM_FORMAT_S32_LE: return SNDRV_PCM_FORMAT_S32_LE; case USB_QMI_PCM_FORMAT_S32_BE: return SNDRV_PCM_FORMAT_S32_BE; case USB_QMI_PCM_FORMAT_U32_LE: return SNDRV_PCM_FORMAT_U32_LE; case USB_QMI_PCM_FORMAT_U32_BE: return SNDRV_PCM_FORMAT_U32_BE; default: /* * We expect the caller to do input validation so we should * never hit this. But we do have to return a proper * snd_pcm_format_t value due to the __bitwise attribute; so * just return the equivalent of 0 in case of bad input.
*/ return SNDRV_PCM_FORMAT_S8;
}
}
/* * Sends QMI disconnect indication message, assumes chip->mutex and qdev_mutex * lock held by caller.
*/ staticint uaudio_send_disconnect_ind(struct snd_usb_audio *chip)
{ struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; struct uaudio_qmi_svc *svc = uaudio_svc; struct uaudio_dev *dev; int ret = 0;
list_for_each_entry(info, head, list) { if (info->start_iova == iova) { if (!info->in_use) return;
found = true;
info->in_use = false; if (info->size == size) goto done;
}
if (found && tmp_size >= info->size) {
info->in_use = false;
tmp_size -= info->size; if (!tmp_size) goto done;
}
}
if (!found) return;
done:
*curr_iova_size += size;
}
/** * uaudio_iommu_unmap() - unmaps iommu memory for adsp * @mtype: ring type * @iova: virtual address to unmap * @iova_size: region size * @mapped_iova_size: mapped region size * * Unmaps the memory region that was previously assigned to the adsp. *
*/ staticvoid uaudio_iommu_unmap(enum mem_type mtype, unsignedlong iova,
size_t iova_size, size_t mapped_iova_size)
{
size_t umap_size; bool unmap = true;
if (!iova || !iova_size) return;
switch (mtype) { case MEM_EVENT_RING: if (uaudio_qdev->er_mapped)
uaudio_qdev->er_mapped = false; else
unmap = false; break;
case MEM_XFER_RING:
uaudio_put_iova(iova, iova_size, &uaudio_qdev->xfer_ring_list,
&uaudio_qdev->xfer_ring_iova_size); break; case MEM_XFER_BUF:
uaudio_put_iova(iova, iova_size, &uaudio_qdev->xfer_buf_list,
&uaudio_qdev->xfer_buf_iova_size); break; default:
unmap = false;
}
if (!unmap || !mapped_iova_size) return;
umap_size = iommu_unmap(uaudio_qdev->data->domain, iova, mapped_iova_size); if (umap_size != mapped_iova_size)
dev_err(uaudio_qdev->data->dev, "unmapped size %zu for iova 0x%08lx of mapped size %zu\n",
umap_size, iova, mapped_iova_size);
}
staticint uaudio_iommu_map_prot(bool dma_coherent)
{ int prot = IOMMU_READ | IOMMU_WRITE;
if (dma_coherent)
prot |= IOMMU_CACHE; return prot;
}
/** * uaudio_iommu_map_pa() - maps iommu memory for adsp * @mtype: ring type * @dma_coherent: dma coherent * @pa: physical address for ring/buffer * @size: size of memory region * * Maps the XHCI related resources to a memory region that is assigned to be * used by the adsp. This will be mapped to the domain, which is created by * the ASoC USB backend driver. *
*/ staticunsignedlong uaudio_iommu_map_pa(enum mem_type mtype, bool dma_coherent,
phys_addr_t pa, size_t size)
{ unsignedlong iova = 0; bool map = true; int prot = uaudio_iommu_map_prot(dma_coherent);
switch (mtype) { case MEM_EVENT_RING:
iova = IOVA_BASE; /* er already mapped */ if (uaudio_qdev->er_mapped)
map = false; break; case MEM_XFER_RING:
iova = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova,
&uaudio_qdev->xfer_ring_iova_size,
&uaudio_qdev->xfer_ring_list, size); break; default:
dev_err(uaudio_qdev->data->dev, "unknown mem type %d\n", mtype);
}
/* looks up alias, if any, for controller DT node and returns the index */ staticint usb_get_controller_id(struct usb_device *udev)
{ if (udev->bus->sysdev && udev->bus->sysdev->of_node) return of_alias_get_id(udev->bus->sysdev->of_node, "usb");
return -ENODEV;
}
/** * uaudio_dev_intf_cleanup() - cleanup transfer resources * @udev: usb device * @info: usb offloading interface * * Cleans up the transfer ring related resources which are assigned per * endpoint from XHCI. This is invoked when the USB endpoints are no * longer in use by the adsp. *
*/ staticvoid uaudio_dev_intf_cleanup(struct usb_device *udev, struct intf_info *info)
{
uaudio_iommu_unmap(MEM_XFER_RING, info->data_xfer_ring_va,
info->data_xfer_ring_size, info->data_xfer_ring_size);
info->data_xfer_ring_va = 0;
info->data_xfer_ring_size = 0;
/** * uaudio_event_ring_cleanup_free() - cleanup secondary event ring * @dev: usb offload device * * Cleans up the secondary event ring that was requested. This will * occur when the adsp is no longer transferring data on the USB bus * across all endpoints. *
*/ staticvoid uaudio_event_ring_cleanup_free(struct uaudio_dev *dev)
{
clear_bit(dev->chip->card->number, &uaudio_qdev->card_slot); /* all audio devices are disconnected */ if (!uaudio_qdev->card_slot) {
uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE,
PAGE_SIZE);
xhci_sideband_remove_interrupter(uadev[dev->chip->card->number].sb);
}
}
staticvoid uaudio_dev_cleanup(struct uaudio_dev *dev)
{ int if_idx;
if (!dev->udev) return;
/* free xfer buffer and unmap xfer ring and buf per interface */ for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { if (!dev->info[if_idx].in_use) continue;
uaudio_dev_intf_cleanup(dev->udev, &dev->info[if_idx]);
dev_dbg(uaudio_qdev->data->dev, "release resources: intf# %d card# %d\n",
dev->info[if_idx].intf_num, dev->chip->card->number);
}
/** * disable_audio_stream() - disable usb snd endpoints * @subs: usb substream * * Closes the USB SND endpoints associated with the current audio stream * used. This will decrement the USB SND endpoint opened reference count. *
*/ staticvoid disable_audio_stream(struct snd_usb_substream *subs)
{ struct snd_usb_audio *chip = subs->stream->chip;
/* QMI service disconnect handlers */ staticvoid qmi_stop_session(void)
{ struct snd_usb_substream *subs; struct usb_host_endpoint *ep; struct snd_usb_audio *chip; struct intf_info *info; int pcm_card_num; int if_idx; int idx;
mutex_lock(&qdev_mutex); /* find all active intf for set alt 0 and cleanup usb audio dev */ for (idx = 0; idx < SNDRV_CARDS; idx++) { if (!atomic_read(&uadev[idx].in_use)) continue;
chip = uadev[idx].chip; for (if_idx = 0; if_idx < uadev[idx].num_intf; if_idx++) { if (!uadev[idx].info || !uadev[idx].info[if_idx].in_use) continue;
info = &uadev[idx].info[if_idx];
pcm_card_num = info->pcm_card_num;
subs = find_substream(pcm_card_num, info->pcm_dev_num,
info->direction); if (!subs || !chip || atomic_read(&chip->shutdown)) {
dev_err(&uadev[idx].udev->dev, "no sub for c#%u dev#%u dir%u\n",
info->pcm_card_num,
info->pcm_dev_num,
info->direction); continue;
} /* Release XHCI endpoints */ if (info->data_ep_pipe)
ep = usb_pipe_endpoint(uadev[pcm_card_num].udev,
info->data_ep_pipe);
xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep);
if (info->sync_ep_pipe)
ep = usb_pipe_endpoint(uadev[pcm_card_num].udev,
info->sync_ep_pipe);
xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep);
/** * uaudio_sideband_notifier() - xHCI sideband event handler * @intf: USB interface handle * @evt: xHCI sideband event type * * This callback is executed when the xHCI sideband encounters a sequence * that requires the sideband clients to take action. An example, is when * xHCI frees the transfer ring, so the client has to ensure that the * offload path is halted. *
*/ staticint uaudio_sideband_notifier(struct usb_interface *intf, struct xhci_sideband_event *evt)
{ struct snd_usb_audio *chip; struct uaudio_dev *dev; int if_idx;
/** * qmi_bye_cb() - qmi bye message callback * @handle: QMI handle * @node: id of the dying node * * This callback is invoked when the QMI bye control message is received * from the QMI client. Handle the message accordingly by ensuring that * the USB offload path is disabled and cleaned up. At this point, ADSP * is not utilizing the USB bus. *
*/ staticvoid qmi_bye_cb(struct qmi_handle *handle, unsignedint node)
{ struct uaudio_qmi_svc *svc = uaudio_svc;
if (svc->uaudio_svc_hdl != handle) return;
if (svc->client_connected && svc->client_sq.sq_node == node) {
qmi_stop_session();
/** * qmi_svc_disconnect_cb() - qmi client disconnected * @handle: QMI handle * @node: id of the dying node * @port: port of the dying client * * Invoked when the remote QMI client is disconnected. Handle this event * the same way as when the QMI bye message is received. This will ensure * the USB offloading path is disabled and cleaned up. *
*/ staticvoid qmi_svc_disconnect_cb(struct qmi_handle *handle, unsignedint node, unsignedint port)
{ struct uaudio_qmi_svc *svc;
if (!uaudio_svc) return;
svc = uaudio_svc; if (svc->uaudio_svc_hdl != handle) return;
/** * enable_audio_stream() - enable usb snd endpoints * @subs: usb substream * @pcm_format: pcm format requested * @channels: number of channels * @cur_rate: sample rate * @datainterval: interval * * Opens all USB SND endpoints used for the data interface. This will increment * the USB SND endpoint's opened count. Requests to keep the interface resumed * until the audio stream is stopped. Will issue the USB set interface control * message to enable the data interface. *
*/ staticint enable_audio_stream(struct snd_usb_substream *subs,
snd_pcm_format_t pcm_format, unsignedint channels, unsignedint cur_rate, int datainterval)
{ struct snd_pcm_hw_params params; struct snd_usb_audio *chip; struct snd_interval *i; struct snd_mask *m; int ret;
chip = subs->stream->chip;
_snd_pcm_hw_params_any(¶ms);
m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT);
snd_mask_leave(m, pcm_format);
/** * uaudio_transfer_buffer_setup() - fetch and populate xfer buffer params * @subs: usb substream * @xfer_buf: xfer buf to be allocated * @xfer_buf_len: size of allocation * @mem_info: QMI response info * * Allocates and maps the transfer buffers that will be utilized by the * audio DSP. Will populate the information in the QMI response that is * sent back to the stream enable request. *
*/ staticint uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, void **xfer_buf_cpu, u32 xfer_buf_len, struct mem_info_v01 *mem_info)
{ struct sg_table xfer_buf_sgt;
dma_addr_t xfer_buf_dma; void *xfer_buf;
u32 len = xfer_buf_len; bool dma_coherent;
dma_addr_t xfer_buf_dma_sysdev;
u32 remainder;
u32 mult; int ret;
/* xfer buffer, multiple of 4K only */ if (!len)
len = PAGE_SIZE;
mult = len / PAGE_SIZE;
remainder = len % PAGE_SIZE;
len = mult * PAGE_SIZE;
len += remainder ? PAGE_SIZE : 0;
if (len > MAX_XFER_BUFF_LEN) {
dev_err(uaudio_qdev->data->dev, "req buf len %d > max buf len %lu, setting %lu\n",
len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN);
len = MAX_XFER_BUFF_LEN;
}
/* get buffer mapped into subs->dev */
xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_dma); if (!xfer_buf) return -ENOMEM;
/* map the physical buffer into sysdev as well */
xfer_buf_dma_sysdev = uaudio_iommu_map_xfer_buf(dma_coherent,
len, &xfer_buf_sgt); if (!xfer_buf_dma_sysdev) {
ret = -ENOMEM; goto unmap_sync;
}
/** * uaudio_endpoint_setup() - fetch and populate endpoint params * @subs: usb substream * @endpoint: usb endpoint to add * @card_num: uadev index * @mem_info: QMI response info * @ep_desc: QMI ep desc response field * * Initialize the USB endpoint being used for a particular USB * stream. Will request XHCI sec intr to reserve the EP for * offloading as well as populating the QMI response with the * transfer ring parameters. *
*/ static phys_addr_t
uaudio_endpoint_setup(struct snd_usb_substream *subs, struct snd_usb_endpoint *endpoint, int card_num, struct mem_info_v01 *mem_info, struct usb_endpoint_descriptor_v01 *ep_desc)
{ struct usb_host_endpoint *ep;
phys_addr_t tr_pa = 0; struct sg_table *sgt; bool dma_coherent; unsignedlong iova; struct page *pg; int ret = -ENODEV;
ep = usb_pipe_endpoint(subs->dev, endpoint->pipe); if (!ep) {
dev_err(uaudio_qdev->data->dev, "data ep # %d context is null\n",
subs->data_endpoint->ep_num); gotoexit;
}
memcpy(ep_desc, &ep->desc, sizeof(ep->desc));
ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep); if (ret < 0) {
dev_err(&subs->dev->dev, "failed to add data ep to sec intr\n");
ret = -ENODEV; gotoexit;
}
sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep); if (!sgt) {
dev_err(&subs->dev->dev, "failed to get data ep ring address\n");
ret = -ENODEV; goto remove_ep;
}
/** * uaudio_event_ring_setup() - fetch and populate event ring params * @subs: usb substream * @card_num: uadev index * @mem_info: QMI response info * * Register secondary interrupter to XHCI and fetch the event buffer info * and populate the information into the QMI response. *
*/ staticint uaudio_event_ring_setup(struct snd_usb_substream *subs, int card_num, struct mem_info_v01 *mem_info)
{ struct sg_table *sgt;
phys_addr_t er_pa; bool dma_coherent; unsignedlong iova; struct page *pg; int ret;
/* event ring */
ret = xhci_sideband_create_interrupter(uadev[card_num].sb, 1, false,
0, uaudio_qdev->data->intr_num); if (ret < 0) {
dev_err(&subs->dev->dev, "failed to fetch interrupter\n"); gotoexit;
}
sgt = xhci_sideband_get_event_buffer(uadev[card_num].sb); if (!sgt) {
dev_err(&subs->dev->dev, "failed to get event ring address\n");
ret = -ENODEV; goto remove_interrupter;
}
/** * uaudio_populate_uac_desc() - parse UAC parameters and populate QMI resp * @subs: usb substream * @resp: QMI response buffer * * Parses information specified within UAC descriptors which explain the * sample parameters that the device expects. This information is populated * to the QMI response sent back to the audio DSP. *
*/ staticint uaudio_populate_uac_desc(struct snd_usb_substream *subs, struct qmi_uaudio_stream_resp_msg_v01 *resp)
{ struct usb_interface_descriptor *altsd; struct usb_host_interface *alts; struct usb_interface *iface; int protocol;
iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface); if (!iface) {
dev_err(&subs->dev->dev, "interface # %d does not exist\n",
subs->cur_audiofmt->iface); return -ENODEV;
}
resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0200);
resp->usb_audio_spec_revision_valid = 1;
} elseif (protocol == UAC_VERSION_3) { if (iface->intf_assoc->bFunctionSubClass ==
UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0) {
dev_err(&subs->dev->dev, "full adc is not supported\n"); return -EINVAL;
}
switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) { case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_16: case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_16: {
resp->usb_audio_subslot_size = 0x2; break;
}
case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_24: case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_24: case UAC3_BADD_EP_MAXPSIZE_ASYNC_MONO_24: case UAC3_BADD_EP_MAXPSIZE_ASYNC_STEREO_24: {
resp->usb_audio_subslot_size = 0x3; break;
}
/** * prepare_qmi_response() - prepare stream enable response * @subs: usb substream * @req_msg: QMI request message * @resp: QMI response buffer * @info_idx: usb interface array index * * Prepares the QMI response for a USB QMI stream enable request. Will parse * out the parameters within the stream enable request, in order to match * requested audio profile to the ones exposed by the USB device connected. * * In addition, will fetch the XHCI transfer resources needed for the handoff to * happen. This includes, transfer ring and buffer addresses and secondary event * ring address. These parameters will be communicated as part of the USB QMI * stream enable response. *
*/ staticint prepare_qmi_response(struct snd_usb_substream *subs, struct qmi_uaudio_stream_req_msg_v01 *req_msg, struct qmi_uaudio_stream_resp_msg_v01 *resp, int info_idx)
{ struct q6usb_offload *data; int pcm_dev_num; int card_num; void *xfer_buf_cpu; int ret;
data = snd_soc_usb_find_priv_data(uaudio_qdev->auxdev->dev.parent); if (!data) {
dev_err(&subs->dev->dev, "No private data found\n"); return -ENODEV;
}
uaudio_qdev->data = data;
resp->std_as_opr_intf_desc_valid = 1;
ret = uaudio_endpoint_setup(subs, subs->data_endpoint, card_num,
&resp->xhci_mem_info.tr_data,
&resp->std_as_data_ep_desc); if (ret < 0) return ret;
resp->std_as_data_ep_desc_valid = 1;
if (subs->sync_endpoint) {
ret = uaudio_endpoint_setup(subs, subs->sync_endpoint, card_num,
&resp->xhci_mem_info.tr_sync,
&resp->std_as_sync_ep_desc); if (ret < 0) goto drop_data_ep;
if (req_msg->service_interval_valid) {
ret = get_data_interval_from_si(subs,
req_msg->service_interval); if (ret == -EINVAL) goto response;
datainterval = ret;
}
uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf;
if (req_msg->enable) {
ret = enable_audio_stream(subs,
map_pcm_format(req_msg->audio_format),
req_msg->number_of_ch, req_msg->bit_rate,
datainterval);
if (!ret)
ret = prepare_qmi_response(subs, req_msg, &resp,
info_idx); if (ret < 0) {
mutex_lock(&chip->mutex);
subs->opened = 0;
mutex_unlock(&chip->mutex);
}
} else {
info = &uadev[pcm_card_num].info[info_idx]; if (info->data_ep_pipe) {
ep = usb_pipe_endpoint(uadev[pcm_card_num].udev,
info->data_ep_pipe); if (ep) {
xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb,
ep);
xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb,
ep);
}
info->data_ep_pipe = 0;
}
if (info->sync_ep_pipe) {
ep = usb_pipe_endpoint(uadev[pcm_card_num].udev,
info->sync_ep_pipe); if (ep) {
xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb,
ep);
xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb,
ep);
}
/** * qc_usb_audio_offload_init_qmi_dev() - initializes qmi dev * * Initializes the USB qdev, which is used to carry information pertaining to * the offloading resources. This device is freed only when there are no longer * any offloading candidates. (i.e, when all audio devices are disconnected) *
*/ staticint qc_usb_audio_offload_init_qmi_dev(void)
{
uaudio_qdev = kzalloc(sizeof(*uaudio_qdev), GFP_KERNEL); if (!uaudio_qdev) return -ENOMEM;
/* initialize xfer ring and xfer buf iova list */
INIT_LIST_HEAD(&uaudio_qdev->xfer_ring_list);
uaudio_qdev->curr_xfer_ring_iova = IOVA_XFER_RING_BASE;
uaudio_qdev->xfer_ring_iova_size =
IOVA_XFER_RING_MAX - IOVA_XFER_RING_BASE;
list_for_each_entry(as, &chip->pcm_list, list) {
subs = &as->substream[SNDRV_PCM_STREAM_PLAYBACK]; if (subs->ep_num) {
sdev->ppcm_idx[idx] = as->pcm->device;
idx++;
} /* * Break if the current index exceeds the number of possible * playback streams counted from the UAC descriptors.
*/ if (idx >= sdev->num_playback) break;
}
return -1;
}
/** * qc_usb_audio_offload_probe() - platform op connect handler * @chip: USB SND device * * Platform connect handler when a USB SND device is detected. Will * notify SOC USB about the connection to enable the USB ASoC backend * and populate internal USB chip array. *
*/ staticvoid qc_usb_audio_offload_probe(struct snd_usb_audio *chip)
{ struct usb_interface *intf = chip->intf[chip->num_interfaces - 1]; struct usb_interface_descriptor *altsd; struct usb_host_interface *alts; struct snd_soc_usb_device *sdev; struct xhci_sideband *sb;
/* * If there is no priv_data, or no playback paths, the connected * device doesn't support offloading. Avoid populating entries for * this device.
*/ if (!snd_soc_usb_find_priv_data(uaudio_qdev->auxdev->dev.parent) ||
!usb_qmi_get_pcm_num(chip, 0)) return;
mutex_lock(&qdev_mutex);
mutex_lock(&chip->mutex); if (!uadev[chip->card->number].chip) {
sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); if (!sdev) gotoexit;
/* Wait until all PCM devices are populated before notifying soc-usb */ if (altsd->bInterfaceNumber == chip->last_iface) {
sdev->num_playback = usb_qmi_get_pcm_num(chip, 0);
/* * Allocate playback pcm index array based on number of possible * playback paths within the UAC descriptors.
*/
sdev->ppcm_idx = kcalloc(sdev->num_playback, sizeof(unsignedint),
GFP_KERNEL); if (!sdev->ppcm_idx) goto unreg_xhci;
/** * qc_usb_audio_cleanup_qmi_dev() - release qmi device * * Frees the USB qdev. Only occurs when there are no longer any potential * devices that can utilize USB audio offloading. *
*/ staticvoid qc_usb_audio_cleanup_qmi_dev(void)
{
kfree(uaudio_qdev);
uaudio_qdev = NULL;
}
/** * qc_usb_audio_offload_disconnect() - platform op disconnect handler * @chip: USB SND device * * Platform disconnect handler. Will ensure that any pending stream is * halted by issuing a QMI disconnect indication packet to the adsp. *
*/ staticvoid qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip)
{ struct uaudio_dev *dev; int card_num;
if (!chip) return;
card_num = chip->card->number; if (card_num >= SNDRV_CARDS) return;
mutex_lock(&qdev_mutex);
mutex_lock(&chip->mutex);
dev = &uadev[card_num];
/* Device has already been cleaned up, or never populated */ if (!dev->chip) {
mutex_unlock(&chip->mutex);
mutex_unlock(&qdev_mutex); return;
}
/* cleaned up already */ if (!dev->udev) goto done;
uaudio_send_disconnect_ind(chip);
uaudio_dev_cleanup(dev);
done: /* * If num_interfaces == 1, the last USB SND interface is being removed. * This is to accommodate for devices w/ multiple UAC functions.
*/ if (chip->num_interfaces == 1) {
snd_soc_usb_disconnect(uaudio_qdev->auxdev->dev.parent, dev->sdev);
xhci_sideband_unregister(dev->sb);
dev->chip = NULL;
kfree(dev->sdev->ppcm_idx);
kfree(dev->sdev);
dev->sdev = NULL;
}
mutex_unlock(&chip->mutex);
mutex_unlock(&qdev_mutex);
}
/** * qc_usb_audio_offload_suspend() - USB offload PM suspend handler * @intf: USB interface * @message: suspend type * * PM suspend handler to ensure that the USB offloading driver is able to stop * any pending traffic, so that the bus can be suspended. *
*/ staticvoid qc_usb_audio_offload_suspend(struct usb_interface *intf,
pm_message_t message)
{ struct snd_usb_audio *chip = usb_get_intfdata(intf); int card_num;
if (!chip) return;
card_num = chip->card->number; if (card_num >= SNDRV_CARDS) return;
/* * Remove all connected devices after unregistering ops, to ensure * that no further connect events will occur. The disconnect routine * will issue the QMI disconnect indication, which results in the * external DSP to stop issuing transfers.
*/
snd_usb_unregister_platform_ops(); for (idx = 0; idx < SNDRV_CARDS; idx++)
qc_usb_audio_offload_disconnect(uadev[idx].chip);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.