1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. 4 */ 5 6 #include <linux/auxiliary_bus.h> 7 #include <linux/device.h> 8 #include <linux/dma-mapping.h> 9 #include <linux/dma-map-ops.h> 10 #include <linux/err.h> 11 #include <linux/init.h> 12 #include <linux/iommu.h> 13 #include <linux/module.h> 14 #include <linux/platform_device.h> 15 #include <linux/slab.h> 16 17 #include <sound/asound.h> 18 #include <sound/jack.h> 19 #include <sound/pcm.h> 20 #include <sound/pcm_params.h> 21 #include <sound/q6usboffload.h> 22 #include <sound/soc.h> 23 #include <sound/soc-usb.h> 24 25 #include <dt-bindings/sound/qcom,q6afe.h> 26 27 #include "q6afe.h" 28 #include "q6dsp-lpass-ports.h" 29 30 #define Q6_USB_SID_MASK 0xF 31 32 struct q6usb_port_data { 33 struct auxiliary_device uauxdev; 34 struct q6afe_usb_cfg usb_cfg; 35 struct snd_soc_usb *usb; 36 struct snd_soc_jack *hs_jack; 37 struct q6usb_offload priv; 38 39 /* Protects against operations between SOC USB and ASoC */ 40 struct mutex mutex; 41 struct list_head devices; 42 }; 43 44 static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = { 45 SND_SOC_DAPM_HP("USB_RX_BE", NULL), 46 }; 47 48 static const struct snd_soc_dapm_route q6usb_dapm_routes[] = { 49 {"USB Playback", NULL, "USB_RX_BE"}, 50 }; 51 52 static int q6usb_hw_params(struct snd_pcm_substream *substream, 53 struct snd_pcm_hw_params *params, 54 struct snd_soc_dai *dai) 55 { 56 struct q6usb_port_data *data = dev_get_drvdata(dai->dev); 57 struct snd_soc_pcm_runtime *rtd = substream->private_data; 58 struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); 59 int direction = substream->stream; 60 struct q6afe_port *q6usb_afe; 61 struct snd_soc_usb_device *sdev; 62 int ret = -EINVAL; 63 64 mutex_lock(&data->mutex); 65 66 /* No active chip index */ 67 if (list_empty(&data->devices)) 68 goto out; 69 70 sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); 71 72 ret = snd_soc_usb_find_supported_format(sdev->chip_idx, params, direction); 73 if (ret < 0) 74 goto out; 75 76 q6usb_afe = q6afe_port_get_from_id(cpu_dai->dev, USB_RX); 77 if (IS_ERR(q6usb_afe)) { 78 ret = PTR_ERR(q6usb_afe); 79 goto out; 80 } 81 82 /* Notify audio DSP about the devices being offloaded */ 83 ret = afe_port_send_usb_dev_param(q6usb_afe, sdev->card_idx, 84 sdev->ppcm_idx[sdev->num_playback - 1]); 85 86 out: 87 mutex_unlock(&data->mutex); 88 89 return ret; 90 } 91 92 static const struct snd_soc_dai_ops q6usb_ops = { 93 .hw_params = q6usb_hw_params, 94 }; 95 96 static struct snd_soc_dai_driver q6usb_be_dais[] = { 97 { 98 .playback = { 99 .stream_name = "USB BE RX", 100 .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 | 101 SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | 102 SNDRV_PCM_RATE_32000 | SNDRV_PCM_RATE_44100 | 103 SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000 | 104 SNDRV_PCM_RATE_192000, 105 .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S16_BE | 106 SNDRV_PCM_FMTBIT_U16_LE | SNDRV_PCM_FMTBIT_U16_BE | 107 SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S24_BE | 108 SNDRV_PCM_FMTBIT_U24_LE | SNDRV_PCM_FMTBIT_U24_BE, 109 .channels_min = 1, 110 .channels_max = 2, 111 .rate_max = 192000, 112 .rate_min = 8000, 113 }, 114 .id = USB_RX, 115 .name = "USB_RX_BE", 116 .ops = &q6usb_ops, 117 }, 118 }; 119 120 static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, 121 const struct of_phandle_args *args, 122 const char **dai_name) 123 { 124 int id = args->args[0]; 125 int ret = -EINVAL; 126 int i; 127 128 for (i = 0; i < ARRAY_SIZE(q6usb_be_dais); i++) { 129 if (q6usb_be_dais[i].id == id) { 130 *dai_name = q6usb_be_dais[i].name; 131 ret = 0; 132 break; 133 } 134 } 135 136 return ret; 137 } 138 139 static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w) 140 { 141 struct snd_soc_card *card = snd_soc_dapm_to_card(w->dapm); 142 struct snd_soc_pcm_runtime *rtd; 143 struct snd_soc_dai *dai; 144 145 for_each_card_rtds(card, rtd) { 146 dai = snd_soc_rtd_to_cpu(rtd, 0); 147 /* 148 * Only look for playback widget. RTD number carries the assigned 149 * PCM index. 150 */ 151 if (dai->stream[0].widget == w) 152 return rtd->id; 153 } 154 155 return -1; 156 } 157 158 static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w) 159 { 160 struct snd_soc_dapm_path *p; 161 162 /* Checks to ensure USB path is enabled/connected */ 163 snd_soc_dapm_widget_for_each_sink_path(w, p) 164 if (!strcmp(p->sink->name, "USB Mixer") && p->connect) 165 return 1; 166 167 return 0; 168 } 169 170 static int q6usb_get_pcm_id(struct snd_soc_component *component) 171 { 172 struct snd_soc_dapm_widget *w; 173 struct snd_soc_dapm_path *p; 174 int pidx; 175 176 /* 177 * Traverse widgets to find corresponding FE widget. The DAI links are 178 * built like the following: 179 * MultiMedia* <-> MM_DL* <-> USB Mixer* 180 */ 181 for_each_card_widgets(component->card, w) { 182 if (!strncmp(w->name, "MultiMedia", 10)) { 183 /* 184 * Look up all paths associated with the FE widget to see if 185 * the USB BE is enabled. The sink widget is responsible to 186 * link with the USB mixers. 187 */ 188 snd_soc_dapm_widget_for_each_sink_path(w, p) { 189 if (q6usb_usb_mixer_enabled(p->sink)) { 190 pidx = q6usb_get_pcm_id_from_widget(w); 191 return pidx; 192 } 193 } 194 } 195 } 196 197 return -1; 198 } 199 200 static int q6usb_update_offload_route(struct snd_soc_component *component, int card, 201 int pcm, int direction, enum snd_soc_usb_kctl path, 202 long *route) 203 { 204 struct q6usb_port_data *data = dev_get_drvdata(component->dev); 205 struct snd_soc_usb_device *sdev; 206 int ret = 0; 207 int idx = -1; 208 209 mutex_lock(&data->mutex); 210 211 if (list_empty(&data->devices) || 212 direction == SNDRV_PCM_STREAM_CAPTURE) { 213 ret = -ENODEV; 214 goto out; 215 } 216 217 sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); 218 219 /* 220 * Will always look for last PCM device discovered/probed as the 221 * active offload index. 222 */ 223 if (card == sdev->card_idx && 224 pcm == sdev->ppcm_idx[sdev->num_playback - 1]) { 225 idx = path == SND_SOC_USB_KCTL_CARD_ROUTE ? 226 component->card->snd_card->number : 227 q6usb_get_pcm_id(component); 228 } 229 230 out: 231 route[0] = idx; 232 mutex_unlock(&data->mutex); 233 234 return ret; 235 } 236 237 static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, 238 struct snd_soc_usb_device *sdev, bool connected) 239 { 240 struct q6usb_port_data *data; 241 242 if (!usb->component) 243 return -ENODEV; 244 245 data = dev_get_drvdata(usb->component->dev); 246 247 mutex_lock(&data->mutex); 248 if (connected) { 249 if (data->hs_jack) 250 snd_jack_report(data->hs_jack->jack, SND_JACK_USB); 251 252 /* Selects the latest USB headset plugged in for offloading */ 253 list_add_tail(&sdev->list, &data->devices); 254 } else { 255 list_del(&sdev->list); 256 257 if (data->hs_jack) 258 snd_jack_report(data->hs_jack->jack, 0); 259 } 260 mutex_unlock(&data->mutex); 261 262 return 0; 263 } 264 265 static void q6usb_component_disable_jack(struct q6usb_port_data *data) 266 { 267 /* Offload jack has already been disabled */ 268 if (!data->hs_jack) 269 return; 270 271 snd_jack_report(data->hs_jack->jack, 0); 272 data->hs_jack = NULL; 273 } 274 275 static void q6usb_component_enable_jack(struct q6usb_port_data *data, 276 struct snd_soc_jack *jack) 277 { 278 snd_jack_report(jack->jack, !list_empty(&data->devices) ? SND_JACK_USB : 0); 279 data->hs_jack = jack; 280 } 281 282 static int q6usb_component_set_jack(struct snd_soc_component *component, 283 struct snd_soc_jack *jack, void *priv) 284 { 285 struct q6usb_port_data *data = dev_get_drvdata(component->dev); 286 287 mutex_lock(&data->mutex); 288 if (jack) 289 q6usb_component_enable_jack(data, jack); 290 else 291 q6usb_component_disable_jack(data); 292 mutex_unlock(&data->mutex); 293 294 return 0; 295 } 296 297 static void q6usb_dai_aux_release(struct device *dev) {} 298 299 static int q6usb_dai_add_aux_device(struct q6usb_port_data *data, 300 struct auxiliary_device *auxdev) 301 { 302 int ret; 303 304 auxdev->dev.parent = data->priv.dev; 305 auxdev->dev.release = q6usb_dai_aux_release; 306 auxdev->name = "qc-usb-audio-offload"; 307 308 ret = auxiliary_device_init(auxdev); 309 if (ret) 310 return ret; 311 312 ret = auxiliary_device_add(auxdev); 313 if (ret) 314 auxiliary_device_uninit(auxdev); 315 316 return ret; 317 } 318 319 static int q6usb_component_probe(struct snd_soc_component *component) 320 { 321 struct q6usb_port_data *data = dev_get_drvdata(component->dev); 322 struct snd_soc_usb *usb; 323 int ret; 324 325 /* Add the QC USB SND aux device */ 326 ret = q6usb_dai_add_aux_device(data, &data->uauxdev); 327 if (ret < 0) 328 return ret; 329 330 usb = snd_soc_usb_allocate_port(component, &data->priv); 331 if (IS_ERR(usb)) 332 return -ENOMEM; 333 334 usb->connection_status_cb = q6usb_alsa_connection_cb; 335 usb->update_offload_route_info = q6usb_update_offload_route; 336 337 snd_soc_usb_add_port(usb); 338 data->usb = usb; 339 340 return 0; 341 } 342 343 static void q6usb_component_remove(struct snd_soc_component *component) 344 { 345 struct q6usb_port_data *data = dev_get_drvdata(component->dev); 346 347 snd_soc_usb_remove_port(data->usb); 348 auxiliary_device_delete(&data->uauxdev); 349 auxiliary_device_uninit(&data->uauxdev); 350 snd_soc_usb_free_port(data->usb); 351 } 352 353 static const struct snd_soc_component_driver q6usb_dai_component = { 354 .probe = q6usb_component_probe, 355 .set_jack = q6usb_component_set_jack, 356 .remove = q6usb_component_remove, 357 .name = "q6usb-dai-component", 358 .dapm_widgets = q6usb_dai_widgets, 359 .num_dapm_widgets = ARRAY_SIZE(q6usb_dai_widgets), 360 .dapm_routes = q6usb_dapm_routes, 361 .num_dapm_routes = ARRAY_SIZE(q6usb_dapm_routes), 362 .of_xlate_dai_name = q6usb_audio_ports_of_xlate_dai_name, 363 }; 364 365 static int q6usb_dai_dev_probe(struct platform_device *pdev) 366 { 367 struct device_node *node = pdev->dev.of_node; 368 struct q6usb_port_data *data; 369 struct device *dev = &pdev->dev; 370 struct of_phandle_args args; 371 int ret; 372 373 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 374 if (!data) 375 return -ENOMEM; 376 377 ret = of_property_read_u16(node, "qcom,usb-audio-intr-idx", 378 &data->priv.intr_num); 379 if (ret) { 380 dev_err(&pdev->dev, "failed to read intr idx.\n"); 381 return ret; 382 } 383 384 ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); 385 if (!ret) 386 data->priv.sid = args.args[0] & Q6_USB_SID_MASK; 387 388 ret = devm_mutex_init(dev, &data->mutex); 389 if (ret < 0) 390 return ret; 391 392 data->priv.domain = iommu_get_domain_for_dev(&pdev->dev); 393 394 data->priv.dev = dev; 395 INIT_LIST_HEAD(&data->devices); 396 dev_set_drvdata(dev, data); 397 398 return devm_snd_soc_register_component(dev, &q6usb_dai_component, 399 q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais)); 400 } 401 402 static const struct of_device_id q6usb_dai_device_id[] = { 403 { .compatible = "qcom,q6usb" }, 404 {}, 405 }; 406 MODULE_DEVICE_TABLE(of, q6usb_dai_device_id); 407 408 static struct platform_driver q6usb_dai_platform_driver = { 409 .driver = { 410 .name = "q6usb-dai", 411 .of_match_table = q6usb_dai_device_id, 412 }, 413 .probe = q6usb_dai_dev_probe, 414 /* 415 * Remove not required as resources are cleaned up as part of 416 * component removal. Others are device managed resources. 417 */ 418 }; 419 module_platform_driver(q6usb_dai_platform_driver); 420 421 MODULE_DESCRIPTION("Q6 USB backend dai driver"); 422 MODULE_LICENSE("GPL"); 423