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_pcm_runtime *rtd; 142 struct snd_soc_dai *dai; 143 144 for_each_card_rtds(w->dapm->card, rtd) { 145 dai = snd_soc_rtd_to_cpu(rtd, 0); 146 /* 147 * Only look for playback widget. RTD number carries the assigned 148 * PCM index. 149 */ 150 if (dai->stream[0].widget == w) 151 return rtd->id; 152 } 153 154 return -1; 155 } 156 157 static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w) 158 { 159 struct snd_soc_dapm_path *p; 160 161 /* Checks to ensure USB path is enabled/connected */ 162 snd_soc_dapm_widget_for_each_sink_path(w, p) 163 if (!strcmp(p->sink->name, "USB Mixer") && p->connect) 164 return 1; 165 166 return 0; 167 } 168 169 static int q6usb_get_pcm_id(struct snd_soc_component *component) 170 { 171 struct snd_soc_dapm_widget *w; 172 struct snd_soc_dapm_path *p; 173 int pidx; 174 175 /* 176 * Traverse widgets to find corresponding FE widget. The DAI links are 177 * built like the following: 178 * MultiMedia* <-> MM_DL* <-> USB Mixer* 179 */ 180 for_each_card_widgets(component->card, w) { 181 if (!strncmp(w->name, "MultiMedia", 10)) { 182 /* 183 * Look up all paths associated with the FE widget to see if 184 * the USB BE is enabled. The sink widget is responsible to 185 * link with the USB mixers. 186 */ 187 snd_soc_dapm_widget_for_each_sink_path(w, p) { 188 if (q6usb_usb_mixer_enabled(p->sink)) { 189 pidx = q6usb_get_pcm_id_from_widget(w); 190 return pidx; 191 } 192 } 193 } 194 } 195 196 return -1; 197 } 198 199 static int q6usb_update_offload_route(struct snd_soc_component *component, int card, 200 int pcm, int direction, enum snd_soc_usb_kctl path, 201 long *route) 202 { 203 struct q6usb_port_data *data = dev_get_drvdata(component->dev); 204 struct snd_soc_usb_device *sdev; 205 int ret = 0; 206 int idx = -1; 207 208 mutex_lock(&data->mutex); 209 210 if (list_empty(&data->devices) || 211 direction == SNDRV_PCM_STREAM_CAPTURE) { 212 ret = -ENODEV; 213 goto out; 214 } 215 216 sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); 217 218 /* 219 * Will always look for last PCM device discovered/probed as the 220 * active offload index. 221 */ 222 if (card == sdev->card_idx && 223 pcm == sdev->ppcm_idx[sdev->num_playback - 1]) { 224 idx = path == SND_SOC_USB_KCTL_CARD_ROUTE ? 225 component->card->snd_card->number : 226 q6usb_get_pcm_id(component); 227 } 228 229 out: 230 route[0] = idx; 231 mutex_unlock(&data->mutex); 232 233 return ret; 234 } 235 236 static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, 237 struct snd_soc_usb_device *sdev, bool connected) 238 { 239 struct q6usb_port_data *data; 240 241 if (!usb->component) 242 return -ENODEV; 243 244 data = dev_get_drvdata(usb->component->dev); 245 246 mutex_lock(&data->mutex); 247 if (connected) { 248 if (data->hs_jack) 249 snd_jack_report(data->hs_jack->jack, SND_JACK_USB); 250 251 /* Selects the latest USB headset plugged in for offloading */ 252 list_add_tail(&sdev->list, &data->devices); 253 } else { 254 list_del(&sdev->list); 255 256 if (data->hs_jack) 257 snd_jack_report(data->hs_jack->jack, 0); 258 } 259 mutex_unlock(&data->mutex); 260 261 return 0; 262 } 263 264 static void q6usb_component_disable_jack(struct q6usb_port_data *data) 265 { 266 /* Offload jack has already been disabled */ 267 if (!data->hs_jack) 268 return; 269 270 snd_jack_report(data->hs_jack->jack, 0); 271 data->hs_jack = NULL; 272 } 273 274 static void q6usb_component_enable_jack(struct q6usb_port_data *data, 275 struct snd_soc_jack *jack) 276 { 277 snd_jack_report(jack->jack, !list_empty(&data->devices) ? SND_JACK_USB : 0); 278 data->hs_jack = jack; 279 } 280 281 static int q6usb_component_set_jack(struct snd_soc_component *component, 282 struct snd_soc_jack *jack, void *priv) 283 { 284 struct q6usb_port_data *data = dev_get_drvdata(component->dev); 285 286 mutex_lock(&data->mutex); 287 if (jack) 288 q6usb_component_enable_jack(data, jack); 289 else 290 q6usb_component_disable_jack(data); 291 mutex_unlock(&data->mutex); 292 293 return 0; 294 } 295 296 static void q6usb_dai_aux_release(struct device *dev) {} 297 298 static int q6usb_dai_add_aux_device(struct q6usb_port_data *data, 299 struct auxiliary_device *auxdev) 300 { 301 int ret; 302 303 auxdev->dev.parent = data->priv.dev; 304 auxdev->dev.release = q6usb_dai_aux_release; 305 auxdev->name = "qc-usb-audio-offload"; 306 307 ret = auxiliary_device_init(auxdev); 308 if (ret) 309 return ret; 310 311 ret = auxiliary_device_add(auxdev); 312 if (ret) 313 auxiliary_device_uninit(auxdev); 314 315 return ret; 316 } 317 318 static int q6usb_component_probe(struct snd_soc_component *component) 319 { 320 struct q6usb_port_data *data = dev_get_drvdata(component->dev); 321 struct snd_soc_usb *usb; 322 int ret; 323 324 /* Add the QC USB SND aux device */ 325 ret = q6usb_dai_add_aux_device(data, &data->uauxdev); 326 if (ret < 0) 327 return ret; 328 329 usb = snd_soc_usb_allocate_port(component, &data->priv); 330 if (IS_ERR(usb)) 331 return -ENOMEM; 332 333 usb->connection_status_cb = q6usb_alsa_connection_cb; 334 usb->update_offload_route_info = q6usb_update_offload_route; 335 336 snd_soc_usb_add_port(usb); 337 data->usb = usb; 338 339 return 0; 340 } 341 342 static void q6usb_component_remove(struct snd_soc_component *component) 343 { 344 struct q6usb_port_data *data = dev_get_drvdata(component->dev); 345 346 snd_soc_usb_remove_port(data->usb); 347 auxiliary_device_delete(&data->uauxdev); 348 auxiliary_device_uninit(&data->uauxdev); 349 snd_soc_usb_free_port(data->usb); 350 } 351 352 static const struct snd_soc_component_driver q6usb_dai_component = { 353 .probe = q6usb_component_probe, 354 .set_jack = q6usb_component_set_jack, 355 .remove = q6usb_component_remove, 356 .name = "q6usb-dai-component", 357 .dapm_widgets = q6usb_dai_widgets, 358 .num_dapm_widgets = ARRAY_SIZE(q6usb_dai_widgets), 359 .dapm_routes = q6usb_dapm_routes, 360 .num_dapm_routes = ARRAY_SIZE(q6usb_dapm_routes), 361 .of_xlate_dai_name = q6usb_audio_ports_of_xlate_dai_name, 362 }; 363 364 static int q6usb_dai_dev_probe(struct platform_device *pdev) 365 { 366 struct device_node *node = pdev->dev.of_node; 367 struct q6usb_port_data *data; 368 struct device *dev = &pdev->dev; 369 struct of_phandle_args args; 370 int ret; 371 372 data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); 373 if (!data) 374 return -ENOMEM; 375 376 ret = of_property_read_u16(node, "qcom,usb-audio-intr-idx", 377 &data->priv.intr_num); 378 if (ret) { 379 dev_err(&pdev->dev, "failed to read intr idx.\n"); 380 return ret; 381 } 382 383 ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); 384 if (!ret) 385 data->priv.sid = args.args[0] & Q6_USB_SID_MASK; 386 387 ret = devm_mutex_init(dev, &data->mutex); 388 if (ret < 0) 389 return ret; 390 391 data->priv.domain = iommu_get_domain_for_dev(&pdev->dev); 392 393 data->priv.dev = dev; 394 INIT_LIST_HEAD(&data->devices); 395 dev_set_drvdata(dev, data); 396 397 return devm_snd_soc_register_component(dev, &q6usb_dai_component, 398 q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais)); 399 } 400 401 static const struct of_device_id q6usb_dai_device_id[] = { 402 { .compatible = "qcom,q6usb" }, 403 {}, 404 }; 405 MODULE_DEVICE_TABLE(of, q6usb_dai_device_id); 406 407 static struct platform_driver q6usb_dai_platform_driver = { 408 .driver = { 409 .name = "q6usb-dai", 410 .of_match_table = q6usb_dai_device_id, 411 }, 412 .probe = q6usb_dai_dev_probe, 413 /* 414 * Remove not required as resources are cleaned up as part of 415 * component removal. Others are device managed resources. 416 */ 417 }; 418 module_platform_driver(q6usb_dai_platform_driver); 419 420 MODULE_DESCRIPTION("Q6 USB backend dai driver"); 421 MODULE_LICENSE("GPL"); 422