Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ASoC: qcom: qdsp6: Fetch USB offload mapped card and PCM device

The USB SND path may need to know how the USB offload path is routed, so
that applications can open the proper sound card and PCM device. The
implementation for the QC ASoC design has a "USB Mixer" kcontrol for each
possible FE (Q6ASM) DAI, which can be utilized to know which front end link
is enabled.

When an application/userspace queries for the mapped offload devices, the
logic will lookup the USB mixer status though the following path:

MultiMedia* <-> MM_DL* <-> USB Mixer*

The "USB Mixer" is a DAPM widget, and the q6routing entity will set the
DAPM connect status accordingly if the USB mixer is enabled. If enabled,
the Q6USB backend link can fetch the PCM device number from the FE DAI
link (Multimedia*). With respects to the card number, that is
straightforward, as the ASoC components have direct references to the ASoC
platform sound card.

An example output can be shown below:

Number of controls: 9
name value
Capture Channel Map 0, 0 (range 0->36)
Playback Channel Map 0, 0 (range 0->36)
Headset Capture Switch On
Headset Capture Volume 1 (range 0->4)
Sidetone Playback Switch On
Sidetone Playback Volume 4096 (range 0->8192)
Headset Playback Switch On
Headset Playback Volume 20, 20 (range 0->24)
USB Offload Playback Route PCM#0 0, 1 (range -1->255)

The "USB Offload Playback Route PCM#*" kcontrol will signify the
corresponding card and pcm device it is offload to. (card#0 pcm - device#1)
If the USB SND device supports multiple audio interfaces, then it will
contain several PCM streams, hence in those situations, it is expected
that there will be multiple playback route kcontrols created.

Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com>
Acked-by: Mark Brown <broonie@kernel.org>
Link: https://lore.kernel.org/r/20250409194804.3773260-27-quic_wcheng@quicinc.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Wesley Cheng and committed by
Greg Kroah-Hartman
e0dd9240 1b8d0d87

+98
+98
sound/soc/qcom/qdsp6/q6usb.c
··· 134 134 return ret; 135 135 } 136 136 137 + static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w) 138 + { 139 + struct snd_soc_pcm_runtime *rtd; 140 + struct snd_soc_dai *dai; 141 + 142 + for_each_card_rtds(w->dapm->card, rtd) { 143 + dai = snd_soc_rtd_to_cpu(rtd, 0); 144 + /* 145 + * Only look for playback widget. RTD number carries the assigned 146 + * PCM index. 147 + */ 148 + if (dai->stream[0].widget == w) 149 + return rtd->id; 150 + } 151 + 152 + return -1; 153 + } 154 + 155 + static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w) 156 + { 157 + struct snd_soc_dapm_path *p; 158 + 159 + /* Checks to ensure USB path is enabled/connected */ 160 + snd_soc_dapm_widget_for_each_sink_path(w, p) 161 + if (!strcmp(p->sink->name, "USB Mixer") && p->connect) 162 + return 1; 163 + 164 + return 0; 165 + } 166 + 167 + static int q6usb_get_pcm_id(struct snd_soc_component *component) 168 + { 169 + struct snd_soc_dapm_widget *w; 170 + struct snd_soc_dapm_path *p; 171 + int pidx; 172 + 173 + /* 174 + * Traverse widgets to find corresponding FE widget. The DAI links are 175 + * built like the following: 176 + * MultiMedia* <-> MM_DL* <-> USB Mixer* 177 + */ 178 + for_each_card_widgets(component->card, w) { 179 + if (!strncmp(w->name, "MultiMedia", 10)) { 180 + /* 181 + * Look up all paths associated with the FE widget to see if 182 + * the USB BE is enabled. The sink widget is responsible to 183 + * link with the USB mixers. 184 + */ 185 + snd_soc_dapm_widget_for_each_sink_path(w, p) { 186 + if (q6usb_usb_mixer_enabled(p->sink)) { 187 + pidx = q6usb_get_pcm_id_from_widget(w); 188 + return pidx; 189 + } 190 + } 191 + } 192 + } 193 + 194 + return -1; 195 + } 196 + 197 + static int q6usb_update_offload_route(struct snd_soc_component *component, int card, 198 + int pcm, int direction, enum snd_soc_usb_kctl path, 199 + long *route) 200 + { 201 + struct q6usb_port_data *data = dev_get_drvdata(component->dev); 202 + struct snd_soc_usb_device *sdev; 203 + int ret = 0; 204 + int idx = -1; 205 + 206 + mutex_lock(&data->mutex); 207 + 208 + if (list_empty(&data->devices) || 209 + direction == SNDRV_PCM_STREAM_CAPTURE) { 210 + ret = -ENODEV; 211 + goto out; 212 + } 213 + 214 + sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); 215 + 216 + /* 217 + * Will always look for last PCM device discovered/probed as the 218 + * active offload index. 219 + */ 220 + if (card == sdev->card_idx && 221 + pcm == sdev->ppcm_idx[sdev->num_playback - 1]) { 222 + idx = path == SND_SOC_USB_KCTL_CARD_ROUTE ? 223 + component->card->snd_card->number : 224 + q6usb_get_pcm_id(component); 225 + } 226 + 227 + out: 228 + route[0] = idx; 229 + mutex_unlock(&data->mutex); 230 + 231 + return ret; 232 + } 233 + 137 234 static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, 138 235 struct snd_soc_usb_device *sdev, bool connected) 139 236 { ··· 329 232 return -ENOMEM; 330 233 331 234 usb->connection_status_cb = q6usb_alsa_connection_cb; 235 + usb->update_offload_route_info = q6usb_update_offload_route; 332 236 333 237 snd_soc_usb_add_port(usb); 334 238 data->usb = usb;