1 | Requesting to see if we can get some Acked-By tags, and merge on usb-next. | ||
---|---|---|---|
2 | |||
1 | Several Qualcomm based chipsets can support USB audio offloading to a | 3 | Several Qualcomm based chipsets can support USB audio offloading to a |
2 | dedicated audio DSP, which can take over issuing transfers to the USB | 4 | dedicated audio DSP, which can take over issuing transfers to the USB |
3 | host controller. The intention is to reduce the load on the main | 5 | host controller. The intention is to reduce the load on the main |
4 | processors in the SoC, and allow them to be placed into lower power modes. | 6 | processors in the SoC, and allow them to be placed into lower power modes. |
5 | There are several parts to this design: | 7 | There are several parts to this design: |
... | ... | ||
36 | ___________V_____________ | | |_______________________| | 38 | ___________V_____________ | | |_______________________| |
37 | |XHCI HCD |<- | | 39 | |XHCI HCD |<- | |
38 | |_________________________| | | 40 | |_________________________| | |
39 | 41 | ||
40 | 42 | ||
41 | Adding ASoC binding layer: | 43 | Adding ASoC binding layer |
44 | ========================= | ||
42 | soc-usb: Intention is to treat a USB port similar to a headphone jack. | 45 | soc-usb: Intention is to treat a USB port similar to a headphone jack. |
43 | The port is always present on the device, but cable/pin status can be | 46 | The port is always present on the device, but cable/pin status can be |
44 | enabled/disabled. Expose mechanisms for USB backend ASoC drivers to | 47 | enabled/disabled. Expose mechanisms for USB backend ASoC drivers to |
45 | communicate with USB SND. | 48 | communicate with USB SND. |
46 | 49 | ||
47 | Create a USB backend for Q6DSP: | 50 | Create a USB backend for Q6DSP |
51 | ============================== | ||
48 | q6usb: Basic backend driver that will be responsible for maintaining the | 52 | q6usb: Basic backend driver that will be responsible for maintaining the |
49 | resources needed to initiate a playback stream using the Q6DSP. Will | 53 | resources needed to initiate a playback stream using the Q6DSP. Will |
50 | be the entity that checks to make sure the connected USB audio device | 54 | be the entity that checks to make sure the connected USB audio device |
51 | supports the requested PCM format. If it does not, the PCM open call will | 55 | supports the requested PCM format. If it does not, the PCM open call will |
52 | fail, and userpsace ALSA can take action accordingly. | 56 | fail, and userspace ALSA can take action accordingly. |
53 | 57 | ||
54 | Introduce XHCI interrupter support: | 58 | Introduce XHCI interrupter support |
59 | ================================== | ||
55 | XHCI HCD supports multiple interrupters, which allows for events to be routed | 60 | XHCI HCD supports multiple interrupters, which allows for events to be routed |
56 | to different event rings. This is determined by "Interrupter Target" field | 61 | to different event rings. This is determined by "Interrupter Target" field |
57 | specified in Section "6.4.1.1 Normal TRB" of the XHCI specification. | 62 | specified in Section "6.4.1.1 Normal TRB" of the XHCI specification. |
58 | 63 | ||
59 | Events in the offloading case will be routed to an event ring that is assigned | 64 | Events in the offloading case will be routed to an event ring that is assigned |
60 | to the audio DSP. | 65 | to the audio DSP. |
61 | 66 | ||
62 | Create vendor ops for the USB SND driver: | 67 | Create vendor ops for the USB SND driver |
68 | ======================================== | ||
63 | qc_audio_offload: This particular driver has several components associated | 69 | qc_audio_offload: This particular driver has several components associated |
64 | with it: | 70 | with it: |
65 | - QMI stream request handler | 71 | - QMI stream request handler |
66 | - XHCI interrupter and resource management | 72 | - XHCI interrupter and resource management |
67 | - audio DSP memory management | 73 | - audio DSP memory management |
... | ... | ||
80 | 86 | ||
81 | and pass it along to the audio DSP. All endpoint management will now be handed | 87 | and pass it along to the audio DSP. All endpoint management will now be handed |
82 | over to the DSP, and the main processor is not involved in transfers. | 88 | over to the DSP, and the main processor is not involved in transfers. |
83 | 89 | ||
84 | Overall, implementing this feature will still expose separate sound card and PCM | 90 | Overall, implementing this feature will still expose separate sound card and PCM |
85 | devices for both the platorm card and USB audio device: | 91 | devices for both the platform card and USB audio device: |
86 | 0 [SM8250MTPWCD938]: sm8250 - SM8250-MTP-WCD9380-WSA8810-VA-D | 92 | 0 [SM8250MTPWCD938]: sm8250 - SM8250-MTP-WCD9380-WSA8810-VA-D |
87 | SM8250-MTP-WCD9380-WSA8810-VA-DMIC | 93 | SM8250-MTP-WCD9380-WSA8810-VA-DMIC |
88 | 1 [Audio ]: USB-Audio - USB Audio | 94 | 1 [Audio ]: USB-Audio - USB Audio |
89 | Generic USB Audio at usb-xhci-hcd.1.auto-1.4, high speed | 95 | Generic USB Audio at usb-xhci-hcd.1.auto-1.4, high speed |
90 | 96 | ||
91 | This is to ensure that userspace ALSA entities can decide which route to take | 97 | This is to ensure that userspace ALSA entities can decide which route to take |
92 | when executing the audio playback. In the above, if card#1 is selected, then | 98 | when executing the audio playback. In the above, if card#1 is selected, then |
93 | USB audio data will take the legacy path over the USB PCM drivers, etc... | 99 | USB audio data will take the legacy path over the USB PCM drivers, etc... |
94 | 100 | ||
101 | The current limitation is that the latest USB audio device that is identified | ||
102 | will be automatically selected by the Q6USB BE DAI for offloading. Future | ||
103 | patches can be added to possibly add for more flexibility, but until the userpace | ||
104 | applications can be better defined, having these mechanisms will complicate the | ||
105 | overall implementation. | ||
106 | |||
107 | USB offload Kcontrols | ||
108 | ===================== | ||
109 | Part of the vendor offload package will have a mixer driver associated with it | ||
110 | (mixer_usb_offload.c). This entity will be responsible for coordinating with | ||
111 | SOC USB and the Q6USB backend DAI to fetch information about the sound card | ||
112 | and PCM device indices associated with the offload path. The logic is done | ||
113 | based on the current implementation of how paths are controlled within the QC | ||
114 | ASoC implementation. | ||
115 | |||
116 | QC ASoC Q6Routing | ||
117 | ----------------- | ||
118 | Within the Q6 ASOC design, the registered ASoC platform card will expose a set | ||
119 | of kcontrols for enabling the BE DAI links to the FE DAI link. For example: | ||
120 | |||
121 | tinymix -D 0 contents | ||
122 | Number of controls: 1033 | ||
123 | ctl type num name value | ||
124 | ... | ||
125 | 1025 BOOL 1 USB Mixer MultiMedia1 Off | ||
126 | 1026 BOOL 1 USB Mixer MultiMedia2 Off | ||
127 | 1027 BOOL 1 USB Mixer MultiMedia3 Off | ||
128 | 1028 BOOL 1 USB Mixer MultiMedia4 Off | ||
129 | 1029 BOOL 1 USB Mixer MultiMedia5 Off | ||
130 | 1030 BOOL 1 USB Mixer MultiMedia6 Off | ||
131 | 1031 BOOL 1 USB Mixer MultiMedia7 Off | ||
132 | 1032 BOOL 1 USB Mixer MultiMedia8 Off | ||
133 | |||
134 | Each of these kcontrols will enable the USB BE DAI link (q6usb) to be connected | ||
135 | to a FE DAI link (q6asm). Since each of these controls are DAPM widgets, when | ||
136 | it is enabled, the DAPM widget's "connect" flag is updated accordingly. | ||
137 | |||
138 | USB Offload Mapping | ||
139 | ------------------- | ||
140 | Based on the Q6routing, the USB BE DAI link can determine which sound card and | ||
141 | PCM device is enabled for offloading. Fetching the ASoC platform sound card's | ||
142 | information is fairly straightforward, and the bulk of the work goes to finding | ||
143 | the corresponding PCM device index. As mentioned above, the USB BE DAI can | ||
144 | traverse the DAPM widgets to find the DAPM path that is related to the control | ||
145 | for the "USB Mixer." Based on which "USB Mixer" is enabled, it can find the | ||
146 | corresponding DAPM widget associated w/ the FE DAI link (Multimedia*). From there | ||
147 | it can find the PCM device created for the Multimedia* stream. | ||
148 | |||
149 | Only one BE DAI link can be enabled per FE DAI. For example, if the HDMI path is | ||
150 | enabled for Multimedia1, the USB Mixer will be disabled and switched over. | ||
151 | |||
152 | Examples of kcontrol | ||
153 | -------------------- | ||
154 | tinymix -D 0 contents | ||
155 | Number of controls: 1033 | ||
156 | ctl type num name | ||
157 | ... | ||
158 | 1025 BOOL 1 USB Mixer MultiMedia1 Off | ||
159 | 1026 BOOL 1 USB Mixer MultiMedia2 On | ||
160 | 1027 BOOL 1 USB Mixer MultiMedia3 Off | ||
161 | 1028 BOOL 1 USB Mixer MultiMedia4 Off | ||
162 | 1029 BOOL 1 USB Mixer MultiMedia5 Off | ||
163 | 1030 BOOL 1 USB Mixer MultiMedia6 Off | ||
164 | 1031 BOOL 1 USB Mixer MultiMedia7 Off | ||
165 | 1032 BOOL 1 USB Mixer MultiMedia8 Off | ||
166 | |||
167 | tinymix -D 2 contents | ||
168 | Number of controls: 10 | ||
169 | ctl type num name value | ||
170 | 0 INT 2 Capture Channel Map 0, 0 (range 0->36) | ||
171 | 1 INT 2 Playback Channel Map 0, 0 (range 0->36) | ||
172 | 2 BOOL 1 Headset Capture Switch On | ||
173 | 3 INT 1 Headset Capture Volume 10 (range 0->13) | ||
174 | 4 BOOL 1 Sidetone Playback Switch On | ||
175 | 5 INT 1 Sidetone Playback Volume 4096 (range 0->8192) | ||
176 | 6 BOOL 1 Headset Playback Switch On | ||
177 | 7 INT 2 Headset Playback Volume 20, 20 (range 0->24) | ||
178 | 8 INT 1 USB Offload Playback Card Route PCM#0 0 (range -1->32) | ||
179 | 9 INT 1 USB Offload Playback PCM Route PCM#0 1 (range -1->255) | ||
180 | |||
181 | The example highlights that the userspace/application can utilize the offload path | ||
182 | for the USB device on card#0 PCM device#1. | ||
183 | |||
184 | When dealing with multiple USB audio devices, only the latest USB device identified | ||
185 | is going to be selected for offload capable. | ||
186 | |||
187 | tinymix -D 1 contents | ||
188 | Number of controls: 9 | ||
189 | ctl type num name value | ||
190 | 0 INT 2 Capture Channel Map 0, 0 (range 0->36) | ||
191 | 1 INT 2 Playback Channel Map 0, 0 (range 0->36) | ||
192 | 2 BOOL 1 Headset Capture Switch On | ||
193 | 3 INT 1 Headset Capture Volume 1 (range 0->4) | ||
194 | 4 BOOL 1 Sidetone Playback Switch On | ||
195 | 5 INT 1 Sidetone Playback Volume 4096 (range 0->8192) | ||
196 | 6 BOOL 1 Headset Playback Switch On | ||
197 | 7 INT 2 Headset Playback Volume 20, 20 (range 0->24) | ||
198 | 8 INT 1 USB Offload Playback Card Route PCM#0 -1 (range -1->32) | ||
199 | 9 INT 1 USB Offload Playback PCM Route PCM#0 -1 (range -1->255) | ||
200 | |||
201 | "-1, -1" shows that this device has no route to the offload path. | ||
202 | |||
95 | This feature was validated using: | 203 | This feature was validated using: |
96 | - tinymix: set/enable the multimedia path to route to USB backend | 204 | - tinymix: set/enable the multimedia path to route to USB backend |
97 | - tinyplay: issue playback on platform card | 205 | - tinyplay: issue playback on platform card |
98 | 206 | ||
99 | Changelog | 207 | Changelog |
100 | -------------------------------------------- | 208 | -------------------------------------------- |
209 | Changes in v35: | ||
210 | - Fixed kernel bot and sparse errors. | ||
211 | |||
212 | Changes in v34: | ||
213 | - Fixed comments to align to kernel doc format. | ||
214 | - Rebased to usb-next | ||
215 | |||
216 | Changes in v33: | ||
217 | - Removed patch#1, which introduced the xhci skip_events flag. Refactored this as | ||
218 | an argument to the xhci_handle_events() API. Events will be skipped accordingly. | ||
219 | - Updated patch#2 and patch#3 to accommodate for the patch#1 removal. | ||
220 | - Replaced sec intr term from xHCI sideband commit message. | ||
221 | |||
222 | Changes in v32: | ||
223 | - Updated license year. Happy new years, 2025! | ||
224 | |||
225 | Changes in v31: | ||
226 | - Rebased to usb-next, which required some minor updates to APIs and structures | ||
227 | changed upstream. | ||
228 | - Moved USB SND offload mixer as part of the QCOM vendor USB offload package. | ||
229 | - Have separate kcontrols for PCM and sound card offload mapping versus one kcontrol | ||
230 | returning a pair. | ||
231 | - Added a xHCI sideband notifier into sideband client drivers, so that clients can | ||
232 | handle certain xHCI sequences properly. Currently, track the xfer ring free, so | ||
233 | the client can ensure transfers are fully stopped by the DSP. | ||
234 | - Updated documentation for #3 | ||
235 | - Removed SoC USB enable/disable sound jack calls, and replaced with direct calls to | ||
236 | SoC jack. | ||
237 | |||
238 | Changes in v30: | ||
239 | - Rebased to usb-next tip | ||
240 | - Renamed the xhci-sideband driver to xhci-sec-intr to avoid confusion with the xHCI | ||
241 | audio sideband feature mentioned within the spec. | ||
242 | - Squashed the xhci-sec-intr change to set IMOD for secondary interrupters into the main | ||
243 | patch that introduces the overall driver. | ||
244 | |||
245 | Changes in v29: | ||
246 | - Fixed some phrases/wording within the SOC USB documentation, and also added an output | ||
247 | with aplay -l for the example output. | ||
248 | - Fixed allocated string buffer for creating the USB SND offload mixer, and added | ||
249 | a PCM index check to ensure that the pcm index is less than the expected number. | ||
250 | - Added a complement enable jack call if USB backend DAI link drivers need access | ||
251 | to it. | ||
252 | |||
253 | Changes in v28: | ||
254 | - Updated comments and commit log in the stop endpoint sync patch. Clarified that | ||
255 | the default stop endpoint completion routine won't fully run as expected since it | ||
256 | has a completion associated w/ the command. | ||
257 | - Added a null check for sb->xhci within xhci_sideband_create_interrupter(). This | ||
258 | is to just ensure that caller has registered sideband before calling create | ||
259 | interrupter. | ||
260 | |||
261 | Changes in v27: | ||
262 | - Added some comments and notes about the offload design. Enforcing the q6routing | ||
263 | to only allow one USB mixer (PCM device) to be enabled at a time. | ||
264 | - Modified SND_JACK_USB notifications for all USB audio offloadable devices plugged | ||
265 | in | ||
266 | - Rebased on latest XHCI secondary interrupter IMOD changes upstream. Modified the | ||
267 | change in this series to allow for XHCI sideband to set the IMOD for sideband | ||
268 | clients. | ||
269 | - Updated documentation on how USB SND kcontrols are involved in the overall design. | ||
270 | - Remove mutex locking from suspend/resume platform ops, as USB core ensures that the | ||
271 | interface and device are in the RPM_ACTIVE state while disconnect is handled. | ||
272 | |||
273 | Changes in v26: | ||
274 | - Cleaned up drivers based on errors from checkpatch | ||
275 | - Fixed several typos using codespell | ||
276 | - Removed any vendor specific notation from USB SND offload mixer patch | ||
277 | |||
278 | Changes in v25: | ||
279 | - Cleanups on typos mentioned within the xHCI layers | ||
280 | - Modified the xHCI interrupter search if clients specify interrupter index | ||
281 | - Moved mixer_usb_offload into its own module, so that other vendor offload USB | ||
282 | modules can utilize it also. | ||
283 | - Added support for USB audio devices that may have multiple PCM streams, as | ||
284 | previous implementation only assumed a single PCM device. SOC USB will be | ||
285 | able to handle an array of PCM indexes supported by the USB audio device. | ||
286 | - Added some additional checks in the QC USB offload driver to check that device | ||
287 | has at least one playback stream before allowing to bind | ||
288 | - Reordered DT bindings to fix the error found by Rob's bot. The patch that | ||
289 | added USB_RX was after the example was updated. | ||
290 | - Updated comments within SOC USB to clarify terminology and to keep it consistent | ||
291 | - Added SND_USB_JACK type for notifying of USB device audio connections | ||
292 | |||
293 | Changes in v24: | ||
294 | - Simplified the kcontrols involved in determining how to utilize the offload | ||
295 | path. | ||
296 | - There is one kcontrol registered to each USB audio device that will | ||
297 | output which card/pcm device it is mapped to for the offload route. | ||
298 | - Removed kcontrols to track offload status and device selection. | ||
299 | - Default to last USB audio device plugged in as offload capable. | ||
300 | - kcontrol will reside on USB SND device. | ||
301 | - Reworked the tracking of connected USB devices from the Q6USB BE DAI link. | ||
302 | Previously, it was convoluted by doing it over an array, but moved to using | ||
303 | a list made it much simpler. Logic is still unchanged in that the last USB | ||
304 | headset plugged in will be selected for offloading. | ||
305 | - Updated the USB SOC RST documentation accordingly with new kcontrol updates. | ||
306 | - Added logic to fetch mapped ASoC card and pcm device index that the offload | ||
307 | path is mapped to for the USB SND kcontrol (for offload route). | ||
308 | - Re-ordered series to hopefully make reviews more readable by combining | ||
309 | patches based on the layer modified (ie QC ASoC, ASoC, USB sound, and USB XHCI). | ||
310 | |||
311 | Changes in v23: | ||
312 | - Added MODULE_DESCRIPTION() fields to drivers that needed it. | ||
313 | |||
314 | Changes in v22: | ||
315 | - Removed components tag for the ASoC platform card, as the USB SND kcontrol for | ||
316 | notifying userspace of offload capable card achieves similar results. | ||
317 | - Due to the above, had to remove the review-by tag for the RST documentation, | ||
318 | as changes were made to remove the components tag section. | ||
319 | - Took in feedback to make the SOC USB add/remove ports void. | ||
320 | - Fixed an issue w/ the USB SND kcontrol management for devices that have multi | ||
321 | UAC interfaces. (would attempt to create the kcontrol more than once) | ||
322 | - Modified SOC USB card and PCM index select to be based off the num_supported | ||
323 | streams that is specified by the USB BE DAI. | ||
324 | - Modified comments on selecting the latest USB headset for offloading. | ||
325 | |||
326 | Changes in v21: | ||
327 | - Added an offload jack disable path from the ASoC platform driver and SOC USB. | ||
328 | - Refactored some of the existing SOC USB context look up APIs and created some | ||
329 | new helpers to search for the USB context. | ||
330 | - Renamed snd_soc_usb_find_format to snd_soc_usb_find_supported_format | ||
331 | - Removed some XHCI sideband calls that would allow clients to actually enable | ||
332 | the IRQ line associated w/ the secondary interrupter. This is removed because | ||
333 | there are other dependencies that are required for that to happen, which are not | ||
334 | covered as part of this series, and to avoid confusion. | ||
335 | - Due to the above, removed the need to export IMOD setting, and enable/disable | ||
336 | interrupter APIs. | ||
337 | |||
338 | Changes in v20: | ||
339 | - Fixed up some formatting changes pointed out in the usb.rst | ||
340 | - Added SB null check during XHCI sideband unregister in case caller passes | ||
341 | improper argument (xhci_sideband_unregister()) | ||
342 | |||
343 | Changes in v19: | ||
344 | - Rebased to usb-next to account for some new changes in dependent drivers. | ||
345 | |||
346 | Changes in v18: | ||
347 | - Rebased to usb-next, which merged in part of the series. Removed these patches. | ||
348 | - Reworked Kconfigs for the ASoC USB related components from QCOM Q6DSP drivers | ||
349 | to keep dependencies in place for SoC USB and USB SND. | ||
350 | - Removed the repurposing of the stop ep sync API into existing XHCI operations. | ||
351 | This will be solely used by the XHCI sideband for now. | ||
352 | |||
353 | Changes in v17: | ||
354 | - Fixed an issue where one patch was squashed into another. | ||
355 | - Re-added some kconfig checks for helpers exposed in USB SND for the soc usb | ||
356 | driver, after running different kconfigs. | ||
357 | |||
358 | Changes in v16: | ||
359 | - Modified some code layer dependencies so that soc usb can be split as a separate | ||
360 | module. | ||
361 | - Split the kcontrols from ASoC QCOM common layer into a separate driver | ||
362 | - Reworked SOC USB kcontrols for controlling card + pcm offload routing and status | ||
363 | so that there are individual controls for card and pcm devices. | ||
364 | - Added a kcontrol remove API in SOC USB to remove the controls on the fly. This | ||
365 | required to add some kcontrol management to SOC USB. | ||
366 | - Removed the disconnect work and workqueue for the QC USB offload as it is not | ||
367 | required, since QMI interface driver ensures events are handled in its own WQ. | ||
368 | |||
369 | Changes in v15: | ||
370 | - Removed some already merged XHCI changes | ||
371 | - Separated SOC USB driver from being always compiled into SOC core. Now | ||
372 | configurable from kconfig. | ||
373 | - Fixed up ASoC kcontrol naming to fit guidelines. | ||
374 | - Removed some unnecessary dummy ifdefs. | ||
375 | - Moved usb snd offload capable kcontrol to be initialized by the platform offloading | ||
376 | driver. | ||
377 | |||
378 | Changes in v14: | ||
379 | - Cleaned up some USB SND related feedback: | ||
380 | - Renamed SNDUSB OFFLD playback available --> USB offload capable card | ||
381 | - Fixed locking while checking if stream is in use | ||
382 | - Replaced some mutex pairs with guard(mutex) | ||
383 | |||
384 | Changes in v13: | ||
385 | - Pulled in secondary/primary interrupter rework from Mathias from: | ||
386 | https://git.kernel.org/pub/scm/linux/kernel/git/mnyman/xhci.git/log/drivers/usb/host?h=fix_eventhandling | ||
387 | - Did some cleanup and commit message updates, and tested on current code base. | ||
388 | - Added mutex locking to xhci sideband to help prevent any race conditions, esp. for when accessing shared | ||
389 | references. | ||
390 | - Addressed concerns from Hillf about gfp_flags and locking used in qc_usb_audio_offload. | ||
391 | - Rebased onto usb-next | ||
392 | |||
393 | Changes in v12: | ||
394 | - Updated copyright year to 2024. Happy new years! | ||
395 | - Fixed newline format on mixer offload driver. | ||
396 | |||
397 | Changes in v11: | ||
398 | - Modified QMI format structures to be const | ||
399 | |||
400 | Changes in v10: | ||
401 | - Added new mixer for exposing kcontrol for sound card created by USB SND. This | ||
402 | allows for applications to know which platform sound card has offload support. | ||
403 | Will return the card number. | ||
404 | - Broke down and cleaned up some functions/APIs within qc_audio_offload driver. | ||
405 | - Exported xhci_initialize_ring_info(), and modified XHCI makefile to allow for | ||
406 | the XHCI sideband to exist as a module. | ||
407 | - Reworked the jack registration and moved it to the QCOM platform card driver, | ||
408 | ie sm8250. | ||
409 | - Added an SOC USB API to fetch a standard component tag that can be appended to | ||
410 | the platform sound card. Added this tag to sm8250 if any USB path exists within | ||
411 | the DT node. | ||
412 | - Moved kcontrols that existed in the Q6USB driver, and made it a bit more generic, | ||
413 | so that naming can be standardized across solutions. SOC USB is now responsible | ||
414 | for creation of these kcontrols. | ||
415 | - Added a SOC USB RST document explaining some code flows and implementation details | ||
416 | so that other vendors can utilize the framework. | ||
417 | - Addressed a case where USB device connection events are lost if usb offload driver | ||
418 | (qc_audio_offload) is not probed when everything else has been initialized, ie | ||
419 | USB SND, SOC USB and ASoC sound card. Add a rediscover device call during module | ||
420 | init, to ensure that connection events will be propagated. | ||
421 | - Rebased to usb-next. | ||
422 | |||
101 | Changes in v9: | 423 | Changes in v9: |
102 | - Fixed the dt binding check issue with regards to num-hc-interrupters. | 424 | - Fixed the dt binding check issue with regards to num-hc-interrupters. |
103 | 425 | ||
104 | Changes in v8: | 426 | Changes in v8: |
105 | - Cleaned up snd_soc_usb_find_priv_data() based on Mark's feedback. Removed some of | 427 | - Cleaned up snd_soc_usb_find_priv_data() based on Mark's feedback. Removed some of |
... | ... | ||
131 | probe calls. | 453 | probe calls. |
132 | - Renamed offload module name and kconfig to fit within the SND domain. | 454 | - Renamed offload module name and kconfig to fit within the SND domain. |
133 | - Renamed attach/detach endpoint API to keep the hw_params notation. | 455 | - Renamed attach/detach endpoint API to keep the hw_params notation. |
134 | 456 | ||
135 | Changes in v5: | 457 | Changes in v5: |
136 | - Removed some unnescessary files that were included | 458 | - Removed some unnecessary files that were included |
137 | - Fixed some typos mentioned | 459 | - Fixed some typos mentioned |
138 | - Addressed dt-binding issues and added hc-interrupters definition to usb-xhci.yaml | 460 | - Addressed dt-binding issues and added hc-interrupters definition to usb-xhci.yaml |
139 | 461 | ||
140 | XHCI: | 462 | XHCI: |
141 | - Moved secondary skip events API to xhci-ring and updated implementation | 463 | - Moved secondary skip events API to xhci-ring and updated implementation |
... | ... | ||
168 | - Removed some fixme tags for conditions that may not be needed/addressed. | 490 | - Removed some fixme tags for conditions that may not be needed/addressed. |
169 | - Repurposed the new endpoint stop sync API to be utilized in other places. | 491 | - Repurposed the new endpoint stop sync API to be utilized in other places. |
170 | - Fixed potential compile issue if XHCI sideband config is not defined. | 492 | - Fixed potential compile issue if XHCI sideband config is not defined. |
171 | 493 | ||
172 | ASoC: | 494 | ASoC: |
173 | - Added sound jack control into the Q6USB driver. Allows for userpsace to know when | 495 | - Added sound jack control into the Q6USB driver. Allows for userspsace to know when |
174 | an offload capable device is connected. | 496 | an offload capable device is connected. |
175 | 497 | ||
176 | USB SND: | 498 | USB SND: |
177 | - Avoided exporting _snd_pcm_hw_param_set based on Takashi's recommendation. | 499 | - Avoided exporting _snd_pcm_hw_param_set based on Takashi's recommendation. |
178 | - Split USB QMI packet header definitions into a separate commit. This is used to | 500 | - Split USB QMI packet header definitions into a separate commit. This is used to |
... | ... | ||
237 | - Created separate dt-bindings for defining USB_RX port. | 559 | - Created separate dt-bindings for defining USB_RX port. |
238 | - Increased APR timeout to accommodate the situation where the AFE port start | 560 | - Increased APR timeout to accommodate the situation where the AFE port start |
239 | command could be delayed due to having to issue a USB bus resume while | 561 | command could be delayed due to having to issue a USB bus resume while |
240 | handling the QMI stream start command. | 562 | handling the QMI stream start command. |
241 | 563 | ||
242 | USB SND: | 564 | Mathias Nyman (1): |
243 | - Added a platform ops during usb_audio_suspend(). This allows for the USB | 565 | xhci: sideband: add initial api to register a secondary interrupter |
244 | offload driver to halt the audio stream when system enters PM suspend. This | 566 | entity |
245 | ensures the audio DSP is not issuing transfers on the USB bus. | ||
246 | - Do not override platform ops if they are already populated. | ||
247 | - Introduce a shared status variable between the USB offload and USB SND layers, | ||
248 | to ensure that only one path is active at a time. If the USB bus is occupied, | ||
249 | then userspace is notified that the path is busy. | ||
250 | |||
251 | Mathias Nyman (4): | ||
252 | xhci: split free interrupter into separate remove and free parts | ||
253 | xhci: add support to allocate several interrupters | ||
254 | xhci: add helper to stop endpoint and wait for completion | ||
255 | xhci: sideband: add initial api to register a sideband entity | ||
256 | 567 | ||
257 | Wesley Cheng (30): | 568 | Wesley Cheng (30): |
258 | usb: host: xhci-mem: Cleanup pending secondary event ring events | 569 | usb: host: xhci-mem: Cleanup pending secondary event ring events |
259 | usb: host: xhci-mem: Allow for interrupter clients to choose specific | 570 | usb: host: xhci-mem: Allow for interrupter clients to choose specific |
260 | index | 571 | index |
261 | ASoC: Add SOC USB APIs for adding an USB backend | 572 | usb: host: xhci-plat: Set XHCI max interrupters if property is present |
573 | usb: host: xhci: Notify xHCI sideband on transfer ring free | ||
574 | usb: dwc3: Specify maximum number of XHCI interrupters | ||
575 | ALSA: Add USB audio device jack type | ||
576 | ALSA: usb-audio: Export USB SND APIs for modules | ||
577 | ALSA: usb-audio: Check for support for requested audio format | ||
578 | ALSA: usb-audio: Save UAC sample size information | ||
579 | ALSA: usb-audio: Prevent starting of audio stream if in use | ||
580 | ALSA: usb-audio: Introduce USB SND platform op callbacks | ||
581 | ALSA: usb-audio: Allow for rediscovery of connected USB SND devices | ||
582 | ASoC: Add SoC USB APIs for adding an USB backend | ||
583 | ASoC: usb: Add PCM format check API for USB backend | ||
584 | ASoC: usb: Create SOC USB SND jack kcontrol | ||
585 | ASoC: usb: Fetch ASoC card and pcm device information | ||
586 | ASoC: usb: Rediscover USB SND devices on USB port add | ||
587 | ASoC: doc: Add documentation for SOC USB | ||
262 | ASoC: dt-bindings: qcom,q6dsp-lpass-ports: Add USB_RX port | 588 | ASoC: dt-bindings: qcom,q6dsp-lpass-ports: Add USB_RX port |
589 | ASoC: dt-bindings: Update example for enabling USB offload on SM8250 | ||
263 | ASoC: qcom: qdsp6: Introduce USB AFE port to q6dsp | 590 | ASoC: qcom: qdsp6: Introduce USB AFE port to q6dsp |
264 | ASoC: qdsp6: q6afe: Increase APR timeout | 591 | ASoC: qcom: qdsp6: q6afe: Increase APR timeout |
265 | ASoC: qcom: qdsp6: Add USB backend ASoC driver for Q6 | 592 | ASoC: qcom: qdsp6: Add USB backend ASoC driver for Q6 |
266 | ALSA: usb-audio: Introduce USB SND platform op callbacks | 593 | ASoC: qcom: qdsp6: Add headphone jack for offload connection status |
267 | ALSA: usb-audio: Export USB SND APIs for modules | 594 | ASoC: qcom: qdsp6: Fetch USB offload mapped card and PCM device |
268 | dt-bindings: usb: xhci: Add num-hc-interrupters definition | ||
269 | dt-bindings: usb: dwc3: Limit num-hc-interrupters definition | ||
270 | usb: dwc3: Specify maximum number of XHCI interrupters | ||
271 | usb: host: xhci-plat: Set XHCI max interrupters if property is present | ||
272 | ALSA: usb-audio: qcom: Add USB QMI definitions | 595 | ALSA: usb-audio: qcom: Add USB QMI definitions |
273 | ALSA: usb-audio: qcom: Introduce QC USB SND offloading support | 596 | ALSA: usb-audio: qcom: Introduce QC USB SND offloading support |
274 | ALSA: usb-audio: Check for support for requested audio format | 597 | ALSA: usb-audio: qcom: Don't allow USB offload path if PCM device is |
275 | ASoC: usb: Add PCM format check API for USB backend | 598 | in use |
276 | ASoC: qcom: qdsp6: Ensure PCM format is supported by USB audio device | 599 | ALSA: usb-audio: qcom: Add USB offload route kcontrol |
277 | ALSA: usb-audio: Prevent starting of audio stream if in use | 600 | ALSA: usb-audio: qcom: Notify USB audio devices on USB offload probing |
278 | ASoC: dt-bindings: Add Q6USB backend | 601 | |
279 | ASoC: dt-bindings: Update example for enabling USB offload on SM8250 | ||
280 | ASoC: qcom: qdsp6: q6afe: Split USB AFE dev_token param into separate | ||
281 | API | ||
282 | ALSA: usb-audio: qcom: Populate PCM and USB chip information | ||
283 | ASoC: qcom: qdsp6: Add support to track available USB PCM devices | ||
284 | ASoC: qcom: qdsp6: Add SND kcontrol to select offload device | ||
285 | ASoC: qcom: qdsp6: Add SND kcontrol for fetching offload status | ||
286 | ASoC: qcom: qdsp6: Add headphone jack for offload connection status | ||
287 | ALSA: usb-audio: qcom: Use card and PCM index from QMI request | ||
288 | ALSA: usb-audio: Allow for rediscovery of connected USB SND devices | ||
289 | ASoC: usb: Rediscover USB SND devices on USB port add | ||
290 | |||
291 | .../devicetree/bindings/sound/qcom,q6usb.yaml | 55 + | ||
292 | .../bindings/sound/qcom,sm8250.yaml | 15 + | 602 | .../bindings/sound/qcom,sm8250.yaml | 15 + |
293 | .../devicetree/bindings/usb/snps,dwc3.yaml | 4 + | 603 | Documentation/sound/soc/index.rst | 1 + |
294 | .../devicetree/bindings/usb/usb-xhci.yaml | 6 + | 604 | Documentation/sound/soc/usb.rst | 482 ++++ |
295 | drivers/usb/dwc3/core.c | 12 + | 605 | drivers/usb/dwc3/core.c | 12 + |
296 | drivers/usb/dwc3/core.h | 2 + | 606 | drivers/usb/dwc3/core.h | 2 + |
297 | drivers/usb/dwc3/host.c | 5 +- | 607 | drivers/usb/dwc3/host.c | 3 + |
298 | drivers/usb/host/Kconfig | 9 + | 608 | drivers/usb/host/Kconfig | 9 + |
299 | drivers/usb/host/Makefile | 4 + | 609 | drivers/usb/host/Makefile | 2 + |
300 | drivers/usb/host/xhci-debugfs.c | 2 +- | 610 | drivers/usb/host/xhci-mem.c | 31 +- |
301 | drivers/usb/host/xhci-mem.c | 136 +- | ||
302 | drivers/usb/host/xhci-plat.c | 2 + | 611 | drivers/usb/host/xhci-plat.c | 2 + |
303 | drivers/usb/host/xhci-ring.c | 53 +- | 612 | drivers/usb/host/xhci-ring.c | 47 +- |
304 | drivers/usb/host/xhci-sideband.c | 372 ++++ | 613 | drivers/usb/host/xhci-sideband.c | 457 ++++ |
305 | drivers/usb/host/xhci.c | 114 +- | 614 | drivers/usb/host/xhci.c | 5 +- |
306 | drivers/usb/host/xhci.h | 18 +- | 615 | drivers/usb/host/xhci.h | 13 +- |
307 | .../sound/qcom,q6dsp-lpass-ports.h | 1 + | 616 | .../sound/qcom,q6dsp-lpass-ports.h | 1 + |
308 | include/linux/usb/xhci-sideband.h | 67 + | 617 | include/linux/mod_devicetable.h | 2 +- |
618 | include/linux/usb/xhci-sideband.h | 102 + | ||
619 | include/sound/jack.h | 4 +- | ||
309 | include/sound/q6usboffload.h | 20 + | 620 | include/sound/q6usboffload.h | 20 + |
310 | include/sound/soc-usb.h | 51 + | 621 | include/sound/soc-usb.h | 138 ++ |
311 | sound/soc/Makefile | 2 +- | 622 | include/uapi/linux/input-event-codes.h | 3 +- |
312 | sound/soc/qcom/Kconfig | 4 + | 623 | sound/core/jack.c | 6 +- |
624 | sound/soc/Kconfig | 10 + | ||
625 | sound/soc/Makefile | 2 + | ||
626 | sound/soc/qcom/Kconfig | 15 + | ||
627 | sound/soc/qcom/Makefile | 2 + | ||
313 | sound/soc/qcom/qdsp6/Makefile | 1 + | 628 | sound/soc/qcom/qdsp6/Makefile | 1 + |
314 | sound/soc/qcom/qdsp6/q6afe-dai.c | 56 + | 629 | sound/soc/qcom/qdsp6/q6afe-dai.c | 60 + |
315 | sound/soc/qcom/qdsp6/q6afe.c | 206 +- | 630 | sound/soc/qcom/qdsp6/q6afe.c | 194 +- |
316 | sound/soc/qcom/qdsp6/q6afe.h | 36 +- | 631 | sound/soc/qcom/qdsp6/q6afe.h | 36 +- |
317 | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 23 + | 632 | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 23 + |
318 | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h | 1 + | 633 | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h | 1 + |
319 | sound/soc/qcom/qdsp6/q6routing.c | 9 + | 634 | sound/soc/qcom/qdsp6/q6routing.c | 32 +- |
320 | sound/soc/qcom/qdsp6/q6usb.c | 450 ++++ | 635 | sound/soc/qcom/qdsp6/q6usb.c | 385 ++++ |
321 | sound/soc/soc-usb.c | 201 ++ | 636 | sound/soc/qcom/sm8250.c | 24 +- |
322 | sound/usb/Kconfig | 15 + | 637 | sound/soc/qcom/usb_offload_utils.c | 56 + |
638 | sound/soc/qcom/usb_offload_utils.h | 30 + | ||
639 | sound/soc/soc-usb.c | 322 +++ | ||
640 | sound/usb/Kconfig | 24 + | ||
323 | sound/usb/Makefile | 2 +- | 641 | sound/usb/Makefile | 2 +- |
324 | sound/usb/card.c | 123 ++ | 642 | sound/usb/card.c | 106 + |
325 | sound/usb/card.h | 23 + | 643 | sound/usb/card.h | 17 + |
326 | sound/usb/endpoint.c | 1 + | 644 | sound/usb/endpoint.c | 1 + |
645 | sound/usb/format.c | 1 + | ||
327 | sound/usb/helper.c | 1 + | 646 | sound/usb/helper.c | 1 + |
328 | sound/usb/pcm.c | 94 +- | 647 | sound/usb/pcm.c | 104 +- |
329 | sound/usb/pcm.h | 11 + | 648 | sound/usb/pcm.h | 11 + |
330 | sound/usb/qcom/Makefile | 2 + | 649 | sound/usb/qcom/Makefile | 6 + |
331 | sound/usb/qcom/qc_audio_offload.c | 1867 +++++++++++++++++ | 650 | sound/usb/qcom/mixer_usb_offload.c | 158 ++ |
332 | sound/usb/qcom/usb_audio_qmi_v01.c | 892 ++++++++ | 651 | sound/usb/qcom/mixer_usb_offload.h | 17 + |
333 | sound/usb/qcom/usb_audio_qmi_v01.h | 162 ++ | 652 | sound/usb/qcom/qc_audio_offload.c | 2005 +++++++++++++++++ |
334 | 43 files changed, 5041 insertions(+), 89 deletions(-) | 653 | sound/usb/qcom/usb_audio_qmi_v01.c | 863 +++++++ |
335 | create mode 100644 Documentation/devicetree/bindings/sound/qcom,q6usb.yaml | 654 | sound/usb/qcom/usb_audio_qmi_v01.h | 164 ++ |
655 | 53 files changed, 5975 insertions(+), 55 deletions(-) | ||
656 | create mode 100644 Documentation/sound/soc/usb.rst | ||
336 | create mode 100644 drivers/usb/host/xhci-sideband.c | 657 | create mode 100644 drivers/usb/host/xhci-sideband.c |
337 | create mode 100644 include/linux/usb/xhci-sideband.h | 658 | create mode 100644 include/linux/usb/xhci-sideband.h |
338 | create mode 100644 include/sound/q6usboffload.h | 659 | create mode 100644 include/sound/q6usboffload.h |
339 | create mode 100644 include/sound/soc-usb.h | 660 | create mode 100644 include/sound/soc-usb.h |
340 | create mode 100644 sound/soc/qcom/qdsp6/q6usb.c | 661 | create mode 100644 sound/soc/qcom/qdsp6/q6usb.c |
662 | create mode 100644 sound/soc/qcom/usb_offload_utils.c | ||
663 | create mode 100644 sound/soc/qcom/usb_offload_utils.h | ||
341 | create mode 100644 sound/soc/soc-usb.c | 664 | create mode 100644 sound/soc/soc-usb.c |
342 | create mode 100644 sound/usb/qcom/Makefile | 665 | create mode 100644 sound/usb/qcom/Makefile |
666 | create mode 100644 sound/usb/qcom/mixer_usb_offload.c | ||
667 | create mode 100644 sound/usb/qcom/mixer_usb_offload.h | ||
343 | create mode 100644 sound/usb/qcom/qc_audio_offload.c | 668 | create mode 100644 sound/usb/qcom/qc_audio_offload.c |
344 | create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.c | 669 | create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.c |
345 | create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.h | 670 | create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.h | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | From: Mathias Nyman <mathias.nyman@linux.intel.com> | ||
2 | 1 | ||
3 | The current function that both removes and frees an interrupter isn't | ||
4 | optimal when using several interrupters. The array of interrupters need | ||
5 | to be protected with a lock while removing interrupters, but the default | ||
6 | xhci spin lock can't be used while freeing the interrupters event ring | ||
7 | segment table as dma_free_coherent() should be called with IRQs enabled. | ||
8 | |||
9 | There is no need to free the interrupter under the lock, so split this | ||
10 | code into separate unlocked free part, and a lock protected remove part. | ||
11 | |||
12 | Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> | ||
13 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | ||
14 | --- | ||
15 | drivers/usb/host/xhci-mem.c | 32 +++++++++++++++++++++----------- | ||
16 | 1 file changed, 21 insertions(+), 11 deletions(-) | ||
17 | |||
18 | diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c | ||
19 | index XXXXXXX..XXXXXXX 100644 | ||
20 | --- a/drivers/usb/host/xhci-mem.c | ||
21 | +++ b/drivers/usb/host/xhci-mem.c | ||
22 | @@ -XXX,XX +XXX,XX @@ int xhci_alloc_erst(struct xhci_hcd *xhci, | ||
23 | } | ||
24 | |||
25 | static void | ||
26 | -xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) | ||
27 | +xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) | ||
28 | { | ||
29 | - struct device *dev = xhci_to_hcd(xhci)->self.sysdev; | ||
30 | - size_t erst_size; | ||
31 | u64 tmp64; | ||
32 | u32 tmp; | ||
33 | |||
34 | if (!ir) | ||
35 | return; | ||
36 | |||
37 | - erst_size = sizeof(struct xhci_erst_entry) * ir->erst.num_entries; | ||
38 | - if (ir->erst.entries) | ||
39 | - dma_free_coherent(dev, erst_size, | ||
40 | - ir->erst.entries, | ||
41 | - ir->erst.erst_dma_addr); | ||
42 | - ir->erst.entries = NULL; | ||
43 | - | ||
44 | /* | ||
45 | * Clean out interrupter registers except ERSTBA. Clearing either the | ||
46 | * low or high 32 bits of ERSTBA immediately causes the controller to | ||
47 | @@ -XXX,XX +XXX,XX @@ xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) | ||
48 | tmp64 &= (u64) ERST_PTR_MASK; | ||
49 | xhci_write_64(xhci, tmp64, &ir->ir_set->erst_dequeue); | ||
50 | } | ||
51 | +} | ||
52 | + | ||
53 | +static void | ||
54 | +xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) | ||
55 | +{ | ||
56 | + struct device *dev = xhci_to_hcd(xhci)->self.sysdev; | ||
57 | + size_t erst_size; | ||
58 | + | ||
59 | + if (!ir) | ||
60 | + return; | ||
61 | + | ||
62 | + erst_size = sizeof(struct xhci_erst_entry) * ir->erst.num_entries; | ||
63 | + if (ir->erst.entries) | ||
64 | + dma_free_coherent(dev, erst_size, | ||
65 | + ir->erst.entries, | ||
66 | + ir->erst.erst_dma_addr); | ||
67 | + ir->erst.entries = NULL; | ||
68 | |||
69 | - /* free interrrupter event ring */ | ||
70 | + /* free interrupter event ring */ | ||
71 | if (ir->event_ring) | ||
72 | xhci_ring_free(xhci, ir->event_ring); | ||
73 | + | ||
74 | ir->event_ring = NULL; | ||
75 | |||
76 | kfree(ir); | ||
77 | @@ -XXX,XX +XXX,XX @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) | ||
78 | |||
79 | cancel_delayed_work_sync(&xhci->cmd_timer); | ||
80 | |||
81 | + xhci_remove_interrupter(xhci, xhci->interrupter); | ||
82 | xhci_free_interrupter(xhci, xhci->interrupter); | ||
83 | xhci->interrupter = NULL; | ||
84 | xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary event ring"); | diff view generated by jsdifflib |
1 | From: Mathias Nyman <mathias.nyman@linux.intel.com> | 1 | From: Mathias Nyman <mathias.nyman@linux.intel.com> |
---|---|---|---|
2 | 2 | ||
3 | Introduce XHCI sideband, which manages the USB endpoints being requested by | 3 | Introduce XHCI sideband, which manages the USB endpoints being requested by |
4 | a client driver. This is used for when client drivers are attempting to | 4 | a client driver. This is used for when client drivers are attempting to |
5 | offload USB endpoints to another entity for handling USB transfers. XHCI | 5 | offload USB endpoints to another entity for handling USB transfers. XHCI |
6 | sideband will allow for drivers to fetch the required information about the | 6 | sec intr will allow for drivers to fetch the required information about the |
7 | transfer ring, so the user can submit transfers independently. Expose the | 7 | transfer ring, so the user can submit transfers independently. Expose the |
8 | required APIs for drivers to register and request for a USB endpoint and to | 8 | required APIs for drivers to register and request for a USB endpoint and to |
9 | manage XHCI secondary interrupters. | 9 | manage XHCI secondary interrupters. |
10 | 10 | ||
11 | Multiple ring segment page linking and proper endpoint clean up added by | 11 | Driver renaming, multiple ring segment page linking, proper endpoint clean |
12 | Wesley Cheng to complete original concept code by Mathias Nyman. | 12 | up, and allowing module compilation added by Wesley Cheng to complete |
13 | original concept code by Mathias Nyman. | ||
13 | 14 | ||
14 | Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> | 15 | Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> |
15 | Co-developed-by: Wesley Cheng <quic_wcheng@quicinc.com> | 16 | Co-developed-by: Wesley Cheng <quic_wcheng@quicinc.com> |
16 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 17 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
17 | --- | 18 | --- |
18 | drivers/usb/host/Kconfig | 9 + | 19 | drivers/usb/host/Kconfig | 9 + |
19 | drivers/usb/host/Makefile | 4 + | 20 | drivers/usb/host/Makefile | 2 + |
20 | drivers/usb/host/xhci-sideband.c | 371 ++++++++++++++++++++++++++++++ | 21 | drivers/usb/host/xhci-sideband.c | 429 ++++++++++++++++++++++++++++++ |
21 | drivers/usb/host/xhci.h | 4 + | 22 | drivers/usb/host/xhci.h | 4 + |
22 | include/linux/usb/xhci-sideband.h | 66 ++++++ | 23 | include/linux/usb/xhci-sideband.h | 74 ++++++ |
23 | 5 files changed, 454 insertions(+) | 24 | 5 files changed, 518 insertions(+) |
24 | create mode 100644 drivers/usb/host/xhci-sideband.c | 25 | create mode 100644 drivers/usb/host/xhci-sideband.c |
25 | create mode 100644 include/linux/usb/xhci-sideband.h | 26 | create mode 100644 include/linux/usb/xhci-sideband.h |
26 | 27 | ||
27 | diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig | 28 | diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig |
28 | index XXXXXXX..XXXXXXX 100644 | 29 | index XXXXXXX..XXXXXXX 100644 |
... | ... | ||
31 | @@ -XXX,XX +XXX,XX @@ config USB_XHCI_RZV2M | 32 | @@ -XXX,XX +XXX,XX @@ config USB_XHCI_RZV2M |
32 | Say 'Y' to enable the support for the xHCI host controller | 33 | Say 'Y' to enable the support for the xHCI host controller |
33 | found in Renesas RZ/V2M SoC. | 34 | found in Renesas RZ/V2M SoC. |
34 | 35 | ||
35 | +config USB_XHCI_SIDEBAND | 36 | +config USB_XHCI_SIDEBAND |
36 | + bool "xHCI support for sideband" | 37 | + tristate "xHCI support for sideband" |
37 | + help | 38 | + help |
38 | + Say 'Y' to enable the support for the xHCI sideband capability. | 39 | + Say 'Y' to enable the support for the xHCI sideband capability. |
39 | + provide a mechanism for a sideband datapath for payload associated | 40 | + Provide a mechanism for a sideband datapath for payload associated |
40 | + with audio class endpoints. This allows for an audio DSP to use | 41 | + with audio class endpoints. This allows for an audio DSP to use |
41 | + xHCI USB endpoints directly, allowing CPU to sleep while playing | 42 | + xHCI USB endpoints directly, allowing CPU to sleep while playing |
42 | + audio | 43 | + audio. |
43 | + | 44 | + |
44 | config USB_XHCI_TEGRA | 45 | config USB_XHCI_TEGRA |
45 | tristate "xHCI support for NVIDIA Tegra SoCs" | 46 | tristate "xHCI support for NVIDIA Tegra SoCs" |
46 | depends on PHY_TEGRA_XUSB | 47 | depends on PHY_TEGRA_XUSB |
47 | diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile | 48 | diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile |
... | ... | ||
50 | +++ b/drivers/usb/host/Makefile | 51 | +++ b/drivers/usb/host/Makefile |
51 | @@ -XXX,XX +XXX,XX @@ endif | 52 | @@ -XXX,XX +XXX,XX @@ endif |
52 | xhci-rcar-hcd-y += xhci-rcar.o | 53 | xhci-rcar-hcd-y += xhci-rcar.o |
53 | xhci-rcar-hcd-$(CONFIG_USB_XHCI_RZV2M) += xhci-rzv2m.o | 54 | xhci-rcar-hcd-$(CONFIG_USB_XHCI_RZV2M) += xhci-rzv2m.o |
54 | 55 | ||
55 | +ifneq ($(CONFIG_USB_XHCI_SIDEBAND),) | 56 | +obj-$(CONFIG_USB_XHCI_SIDEBAND) += xhci-sideband.o |
56 | + xhci-hcd-y += xhci-sideband.o | ||
57 | +endif | ||
58 | + | 57 | + |
59 | obj-$(CONFIG_USB_PCI) += pci-quirks.o | 58 | obj-$(CONFIG_USB_PCI) += pci-quirks.o |
60 | 59 | ||
61 | obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o | 60 | obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o |
62 | diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c | 61 | diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c |
... | ... | ||
68 | +// SPDX-License-Identifier: GPL-2.0 | 67 | +// SPDX-License-Identifier: GPL-2.0 |
69 | + | 68 | + |
70 | +/* | 69 | +/* |
71 | + * xHCI host controller sideband support | 70 | + * xHCI host controller sideband support |
72 | + * | 71 | + * |
73 | + * Copyright (c) 2023, Intel Corporation. | 72 | + * Copyright (c) 2023-2025, Intel Corporation. |
74 | + * | 73 | + * |
75 | + * Author: Mathias Nyman | 74 | + * Author: Mathias Nyman |
76 | + */ | 75 | + */ |
77 | + | 76 | + |
78 | +#include <linux/usb/xhci-sideband.h> | 77 | +#include <linux/usb/xhci-sideband.h> |
... | ... | ||
97 | + n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT; | 96 | + n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT; |
98 | + pages = kvmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL); | 97 | + pages = kvmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL); |
99 | + if (!pages) | 98 | + if (!pages) |
100 | + return NULL; | 99 | + return NULL; |
101 | + | 100 | + |
102 | + sgt = kzalloc(sizeof(struct sg_table), GFP_KERNEL); | 101 | + sgt = kzalloc(sizeof(*sgt), GFP_KERNEL); |
103 | + if (!sgt) { | 102 | + if (!sgt) { |
104 | + kvfree(pages); | 103 | + kvfree(pages); |
105 | + return NULL; | 104 | + return NULL; |
106 | + } | 105 | + } |
107 | + | 106 | + |
108 | + seg = ring->first_seg; | 107 | + seg = ring->first_seg; |
108 | + if (!seg) | ||
109 | + goto err; | ||
109 | + /* | 110 | + /* |
110 | + * Rings can potentially have multiple segments, create an array that | 111 | + * Rings can potentially have multiple segments, create an array that |
111 | + * carries page references to allocated segments. Utilize the | 112 | + * carries page references to allocated segments. Utilize the |
112 | + * sg_alloc_table_from_pages() to create the sg table, and to ensure | 113 | + * sg_alloc_table_from_pages() to create the sg table, and to ensure |
113 | + * that page links are created. | 114 | + * that page links are created. |
114 | + */ | 115 | + */ |
115 | + for (i = 0; i < ring->num_segs; i++) { | 116 | + for (i = 0; i < ring->num_segs; i++) { |
116 | + dma_get_sgtable(dev, sgt, seg->trbs, seg->dma, | 117 | + dma_get_sgtable(dev, sgt, seg->trbs, seg->dma, |
117 | + TRB_SEGMENT_SIZE); | 118 | + TRB_SEGMENT_SIZE); |
118 | + pages[i] = sg_page(sgt->sgl); | 119 | + pages[i] = sg_page(sgt->sgl); |
119 | + sg_free_table(sgt); | 120 | + sg_free_table(sgt); |
120 | + seg = seg->next; | 121 | + seg = seg->next; |
121 | + } | 122 | + } |
122 | + | 123 | + |
123 | + if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) { | 124 | + if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) |
124 | + kvfree(pages); | 125 | + goto err; |
125 | + kfree(sgt); | 126 | + |
126 | + | ||
127 | + return NULL; | ||
128 | + } | ||
129 | + /* | 127 | + /* |
130 | + * Save first segment dma address to sg dma_address field for the sideband | 128 | + * Save first segment dma address to sg dma_address field for the sideband |
131 | + * client to have access to the IOVA of the ring. | 129 | + * client to have access to the IOVA of the ring. |
132 | + */ | 130 | + */ |
133 | + sg_dma_address(sgt->sgl) = ring->first_seg->dma; | 131 | + sg_dma_address(sgt->sgl) = ring->first_seg->dma; |
134 | + | 132 | + |
135 | + return sgt; | 133 | + return sgt; |
134 | + | ||
135 | +err: | ||
136 | + kvfree(pages); | ||
137 | + kfree(sgt); | ||
138 | + | ||
139 | + return NULL; | ||
136 | +} | 140 | +} |
137 | + | 141 | + |
138 | +static void | 142 | +static void |
139 | +__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep) | 143 | +__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep) |
140 | +{ | 144 | +{ |
... | ... | ||
167 | + struct usb_host_endpoint *host_ep) | 171 | + struct usb_host_endpoint *host_ep) |
168 | +{ | 172 | +{ |
169 | + struct xhci_virt_ep *ep; | 173 | + struct xhci_virt_ep *ep; |
170 | + unsigned int ep_index; | 174 | + unsigned int ep_index; |
171 | + | 175 | + |
176 | + mutex_lock(&sb->mutex); | ||
172 | + ep_index = xhci_get_endpoint_index(&host_ep->desc); | 177 | + ep_index = xhci_get_endpoint_index(&host_ep->desc); |
173 | + ep = &sb->vdev->eps[ep_index]; | 178 | + ep = &sb->vdev->eps[ep_index]; |
174 | + | 179 | + |
175 | + if (ep->ep_state & EP_HAS_STREAMS) | 180 | + if (ep->ep_state & EP_HAS_STREAMS) { |
181 | + mutex_unlock(&sb->mutex); | ||
176 | + return -EINVAL; | 182 | + return -EINVAL; |
183 | + } | ||
177 | + | 184 | + |
178 | + /* | 185 | + /* |
179 | + * Note, we don't know the DMA mask of the audio DSP device, if its | 186 | + * Note, we don't know the DMA mask of the audio DSP device, if its |
180 | + * smaller than for xhci it won't be able to access the endpoint ring | 187 | + * smaller than for xhci it won't be able to access the endpoint ring |
181 | + * buffer. This could be solved by not allowing the audio class driver | 188 | + * buffer. This could be solved by not allowing the audio class driver |
182 | + * to add the endpoint the normal way, but instead offload it immediately, | 189 | + * to add the endpoint the normal way, but instead offload it immediately, |
183 | + * and let this function add the endpoint and allocate the ring buffer | 190 | + * and let this function add the endpoint and allocate the ring buffer |
184 | + * with the smallest common DMA mask | 191 | + * with the smallest common DMA mask |
185 | + */ | 192 | + */ |
186 | + | 193 | + if (sb->eps[ep_index] || ep->sideband) { |
187 | + if (sb->eps[ep_index] || ep->sideband) | 194 | + mutex_unlock(&sb->mutex); |
188 | + return -EBUSY; | 195 | + return -EBUSY; |
196 | + } | ||
189 | + | 197 | + |
190 | + ep->sideband = sb; | 198 | + ep->sideband = sb; |
191 | + sb->eps[ep_index] = ep; | 199 | + sb->eps[ep_index] = ep; |
200 | + mutex_unlock(&sb->mutex); | ||
192 | + | 201 | + |
193 | + return 0; | 202 | + return 0; |
194 | +} | 203 | +} |
195 | +EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint); | 204 | +EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint); |
196 | + | 205 | + |
... | ... | ||
211 | + struct usb_host_endpoint *host_ep) | 220 | + struct usb_host_endpoint *host_ep) |
212 | +{ | 221 | +{ |
213 | + struct xhci_virt_ep *ep; | 222 | + struct xhci_virt_ep *ep; |
214 | + unsigned int ep_index; | 223 | + unsigned int ep_index; |
215 | + | 224 | + |
225 | + mutex_lock(&sb->mutex); | ||
216 | + ep_index = xhci_get_endpoint_index(&host_ep->desc); | 226 | + ep_index = xhci_get_endpoint_index(&host_ep->desc); |
217 | + ep = sb->eps[ep_index]; | 227 | + ep = sb->eps[ep_index]; |
218 | + | 228 | + |
219 | + if (!ep || !ep->sideband) | 229 | + if (!ep || !ep->sideband || ep->sideband != sb) { |
230 | + mutex_unlock(&sb->mutex); | ||
220 | + return -ENODEV; | 231 | + return -ENODEV; |
232 | + } | ||
221 | + | 233 | + |
222 | + __xhci_sideband_remove_endpoint(sb, ep); | 234 | + __xhci_sideband_remove_endpoint(sb, ep); |
223 | + xhci_initialize_ring_info(ep->ring, 1); | 235 | + xhci_initialize_ring_info(ep->ring); |
236 | + mutex_unlock(&sb->mutex); | ||
224 | + | 237 | + |
225 | + return 0; | 238 | + return 0; |
226 | +} | 239 | +} |
227 | +EXPORT_SYMBOL_GPL(xhci_sideband_remove_endpoint); | 240 | +EXPORT_SYMBOL_GPL(xhci_sideband_remove_endpoint); |
228 | + | 241 | + |
... | ... | ||
234 | + unsigned int ep_index; | 247 | + unsigned int ep_index; |
235 | + | 248 | + |
236 | + ep_index = xhci_get_endpoint_index(&host_ep->desc); | 249 | + ep_index = xhci_get_endpoint_index(&host_ep->desc); |
237 | + ep = sb->eps[ep_index]; | 250 | + ep = sb->eps[ep_index]; |
238 | + | 251 | + |
239 | + if (!ep || ep->sideband != sb) | 252 | + if (!ep || !ep->sideband || ep->sideband != sb) |
240 | + return -EINVAL; | 253 | + return -EINVAL; |
241 | + | 254 | + |
242 | + return xhci_stop_endpoint_sync(sb->xhci, ep, 0); | 255 | + return xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL); |
243 | +} | 256 | +} |
244 | +EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint); | 257 | +EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint); |
245 | + | 258 | + |
246 | +/** | 259 | +/** |
247 | + * xhci_sideband_get_endpoint_buffer - gets the endpoint transfer buffer address | 260 | + * xhci_sideband_get_endpoint_buffer - gets the endpoint transfer buffer address |
248 | + * @sb: sideband instance for this usb device | 261 | + * @sb: sideband instance for this usb device |
249 | + * @host_ep: usb host endpoint | 262 | + * @host_ep: usb host endpoint |
250 | + * | 263 | + * |
251 | + * Returns the address of the endpoint buffer where xHC controller reads queued | 264 | + * Returns the address of the endpoint buffer where xHC controller reads queued |
252 | + * transfer TRBs from. This is the starting address of the ringbuffer where the | 265 | + * transfer TRBs from. This is the starting address of the ringbuffer where the |
253 | + * sidband cliend should write TRBs to. | 266 | + * sideband client should write TRBs to. |
254 | + * | 267 | + * |
255 | + * Caller needs to free the returned sg_table | 268 | + * Caller needs to free the returned sg_table |
256 | + * | 269 | + * |
257 | + * Return: struct sg_table * if successful. NULL otherwise. | 270 | + * Return: struct sg_table * if successful. NULL otherwise. |
258 | + */ | 271 | + */ |
259 | +struct sg_table * | 272 | +struct sg_table * |
260 | +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, | 273 | +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, |
261 | + struct usb_host_endpoint *host_ep) | 274 | + struct usb_host_endpoint *host_ep) |
262 | +{ | 275 | +{ |
263 | + struct xhci_virt_ep *ep; | 276 | + struct xhci_virt_ep *ep; |
264 | + unsigned int ep_index; | 277 | + unsigned int ep_index; |
265 | + | 278 | + |
266 | + ep_index = xhci_get_endpoint_index(&host_ep->desc); | 279 | + ep_index = xhci_get_endpoint_index(&host_ep->desc); |
267 | + ep = sb->eps[ep_index]; | 280 | + ep = sb->eps[ep_index]; |
268 | + | 281 | + |
269 | + if (!ep) | 282 | + if (!ep || !ep->ring || !ep->sideband || ep->sideband != sb) |
270 | + return NULL; | 283 | + return NULL; |
271 | + | 284 | + |
272 | + return xhci_ring_to_sgtable(sb, ep->ring); | 285 | + return xhci_ring_to_sgtable(sb, ep->ring); |
273 | +} | 286 | +} |
274 | +EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer); | 287 | +EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer); |
... | ... | ||
286 | + * Return: struct sg_table * if successful. NULL otherwise. | 299 | + * Return: struct sg_table * if successful. NULL otherwise. |
287 | + */ | 300 | + */ |
288 | +struct sg_table * | 301 | +struct sg_table * |
289 | +xhci_sideband_get_event_buffer(struct xhci_sideband *sb) | 302 | +xhci_sideband_get_event_buffer(struct xhci_sideband *sb) |
290 | +{ | 303 | +{ |
291 | + if (!sb->ir) | 304 | + if (!sb || !sb->ir) |
292 | + return NULL; | 305 | + return NULL; |
293 | + | 306 | + |
294 | + return xhci_ring_to_sgtable(sb, sb->ir->event_ring); | 307 | + return xhci_ring_to_sgtable(sb, sb->ir->event_ring); |
295 | +} | 308 | +} |
296 | +EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); | 309 | +EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); |
297 | + | 310 | + |
298 | +/** | 311 | +/** |
299 | + * xhci_sideband_create_interrupter - creates a new interrupter for this sideband | 312 | + * xhci_sideband_create_interrupter - creates a new interrupter for this sideband |
300 | + * @sb: sideband instance for this usb device | 313 | + * @sb: sideband instance for this usb device |
314 | + * @num_seg: number of event ring segments to allocate | ||
315 | + * @ip_autoclear: IP autoclearing support such as MSI implemented | ||
301 | + * | 316 | + * |
302 | + * Sets up a xhci interrupter that can be used for this sideband accessed usb | 317 | + * Sets up a xhci interrupter that can be used for this sideband accessed usb |
303 | + * device. Transfer events for this device can be routed to this interrupters | 318 | + * device. Transfer events for this device can be routed to this interrupters |
304 | + * event ring by setting the 'Interrupter Target' field correctly when queueing | 319 | + * event ring by setting the 'Interrupter Target' field correctly when queueing |
305 | + * the transfer TRBs. | 320 | + * the transfer TRBs. |
306 | + * Once this interrupter is created the interrupter target ID can be obtained | 321 | + * Once this interrupter is created the interrupter target ID can be obtained |
307 | + * by calling xhci_sideband_interrupter_id() | 322 | + * by calling xhci_sideband_interrupter_id() |
308 | + * | 323 | + * |
309 | + * Returns 0 on success, negative error otherwise | 324 | + * Returns 0 on success, negative error otherwise |
310 | + */ | 325 | + */ |
311 | +int | 326 | +int |
312 | +xhci_sideband_create_interrupter(struct xhci_sideband *sb) | 327 | +xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, |
313 | +{ | 328 | + bool ip_autoclear, u32 imod_interval) |
314 | + if (sb->ir) | 329 | +{ |
315 | + return -EBUSY; | 330 | + int ret = 0; |
316 | + | 331 | + |
317 | + sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci)); | 332 | + if (!sb || !sb->xhci) |
318 | + if (!sb->ir) | 333 | + return -ENODEV; |
319 | + return -ENOMEM; | 334 | + |
320 | + | 335 | + mutex_lock(&sb->mutex); |
321 | + return 0; | 336 | + if (sb->ir) { |
337 | + ret = -EBUSY; | ||
338 | + goto out; | ||
339 | + } | ||
340 | + | ||
341 | + sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci), | ||
342 | + num_seg, imod_interval); | ||
343 | + if (!sb->ir) { | ||
344 | + ret = -ENOMEM; | ||
345 | + goto out; | ||
346 | + } | ||
347 | + | ||
348 | + sb->ir->ip_autoclear = ip_autoclear; | ||
349 | + | ||
350 | +out: | ||
351 | + mutex_unlock(&sb->mutex); | ||
352 | + | ||
353 | + return ret; | ||
322 | +} | 354 | +} |
323 | +EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter); | 355 | +EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter); |
324 | + | 356 | + |
325 | +/** | 357 | +/** |
326 | + * xhci_sideband_remove_interrupter - remove the interrupter from a sideband | 358 | + * xhci_sideband_remove_interrupter - remove the interrupter from a sideband |
... | ... | ||
333 | +xhci_sideband_remove_interrupter(struct xhci_sideband *sb) | 365 | +xhci_sideband_remove_interrupter(struct xhci_sideband *sb) |
334 | +{ | 366 | +{ |
335 | + if (!sb || !sb->ir) | 367 | + if (!sb || !sb->ir) |
336 | + return; | 368 | + return; |
337 | + | 369 | + |
370 | + mutex_lock(&sb->mutex); | ||
338 | + xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); | 371 | + xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); |
339 | + | 372 | + |
340 | + sb->ir = NULL; | 373 | + sb->ir = NULL; |
374 | + mutex_unlock(&sb->mutex); | ||
341 | +} | 375 | +} |
342 | +EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter); | 376 | +EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter); |
343 | + | 377 | + |
344 | +/** | 378 | +/** |
345 | + * xhci_sideband_interrupter_id - return the interrupter target id | 379 | + * xhci_sideband_interrupter_id - return the interrupter target id |
... | ... | ||
363 | +} | 397 | +} |
364 | +EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id); | 398 | +EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id); |
365 | + | 399 | + |
366 | +/** | 400 | +/** |
367 | + * xhci_sideband_register - register a sideband for a usb device | 401 | + * xhci_sideband_register - register a sideband for a usb device |
368 | + * @udev: usb device to be accessed via sideband | 402 | + * @intf: usb interface associated with the sideband device |
369 | + * | 403 | + * |
370 | + * Allows for clients to utilize XHCI interrupters and fetch transfer and event | 404 | + * Allows for clients to utilize XHCI interrupters and fetch transfer and event |
371 | + * ring parameters for executing data transfers. | 405 | + * ring parameters for executing data transfers. |
372 | + * | 406 | + * |
373 | + * Return: pointer to a new xhci_sideband instance if successful. NULL otherwise. | 407 | + * Return: pointer to a new xhci_sideband instance if successful. NULL otherwise. |
374 | + */ | 408 | + */ |
375 | +struct xhci_sideband * | 409 | +struct xhci_sideband * |
376 | +xhci_sideband_register(struct usb_device *udev) | 410 | +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type) |
377 | +{ | 411 | +{ |
412 | + struct usb_device *udev = interface_to_usbdev(intf); | ||
378 | + struct usb_hcd *hcd = bus_to_hcd(udev->bus); | 413 | + struct usb_hcd *hcd = bus_to_hcd(udev->bus); |
379 | + struct xhci_hcd *xhci = hcd_to_xhci(hcd); | 414 | + struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
380 | + struct xhci_virt_device *vdev; | 415 | + struct xhci_virt_device *vdev; |
381 | + struct xhci_sideband *sb; | 416 | + struct xhci_sideband *sb; |
382 | + | 417 | + |
383 | + /* make sure the usb device is connected to a xhci controller */ | 418 | + /* |
384 | + if (!udev->slot_id) | 419 | + * Make sure the usb device is connected to a xhci controller. Fail |
420 | + * registration if the type is anything other than XHCI_SIDEBAND_VENDOR, | ||
421 | + * as this is the only type that is currently supported by xhci-sideband. | ||
422 | + */ | ||
423 | + if (!udev->slot_id || type != XHCI_SIDEBAND_VENDOR) | ||
385 | + return NULL; | 424 | + return NULL; |
386 | + | 425 | + |
387 | + sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev)); | 426 | + sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev)); |
388 | + if (!sb) | 427 | + if (!sb) |
389 | + return NULL; | 428 | + return NULL; |
429 | + | ||
430 | + mutex_init(&sb->mutex); | ||
390 | + | 431 | + |
391 | + /* check this device isn't already controlled via sideband */ | 432 | + /* check this device isn't already controlled via sideband */ |
392 | + spin_lock_irq(&xhci->lock); | 433 | + spin_lock_irq(&xhci->lock); |
393 | + | 434 | + |
394 | + vdev = xhci->devs[udev->slot_id]; | 435 | + vdev = xhci->devs[udev->slot_id]; |
... | ... | ||
401 | + return NULL; | 442 | + return NULL; |
402 | + } | 443 | + } |
403 | + | 444 | + |
404 | + sb->xhci = xhci; | 445 | + sb->xhci = xhci; |
405 | + sb->vdev = vdev; | 446 | + sb->vdev = vdev; |
447 | + sb->intf = intf; | ||
448 | + sb->type = type; | ||
406 | + vdev->sideband = sb; | 449 | + vdev->sideband = sb; |
407 | + | 450 | + |
408 | + spin_unlock_irq(&xhci->lock); | 451 | + spin_unlock_irq(&xhci->lock); |
409 | + | 452 | + |
410 | + return sb; | 453 | + return sb; |
... | ... | ||
422 | + * the buffers. | 465 | + * the buffers. |
423 | + */ | 466 | + */ |
424 | +void | 467 | +void |
425 | +xhci_sideband_unregister(struct xhci_sideband *sb) | 468 | +xhci_sideband_unregister(struct xhci_sideband *sb) |
426 | +{ | 469 | +{ |
470 | + struct xhci_hcd *xhci; | ||
427 | + int i; | 471 | + int i; |
428 | + | 472 | + |
473 | + if (!sb) | ||
474 | + return; | ||
475 | + | ||
476 | + xhci = sb->xhci; | ||
477 | + | ||
478 | + mutex_lock(&sb->mutex); | ||
429 | + for (i = 0; i < EP_CTX_PER_DEV; i++) | 479 | + for (i = 0; i < EP_CTX_PER_DEV; i++) |
430 | + if (sb->eps[i]) | 480 | + if (sb->eps[i]) |
431 | + __xhci_sideband_remove_endpoint(sb, sb->eps[i]); | 481 | + __xhci_sideband_remove_endpoint(sb, sb->eps[i]); |
482 | + mutex_unlock(&sb->mutex); | ||
432 | + | 483 | + |
433 | + xhci_sideband_remove_interrupter(sb); | 484 | + xhci_sideband_remove_interrupter(sb); |
434 | + | 485 | + |
486 | + spin_lock_irq(&xhci->lock); | ||
487 | + sb->xhci = NULL; | ||
435 | + sb->vdev->sideband = NULL; | 488 | + sb->vdev->sideband = NULL; |
489 | + spin_unlock_irq(&xhci->lock); | ||
490 | + | ||
436 | + kfree(sb); | 491 | + kfree(sb); |
437 | +} | 492 | +} |
438 | +EXPORT_SYMBOL_GPL(xhci_sideband_unregister); | 493 | +EXPORT_SYMBOL_GPL(xhci_sideband_unregister); |
494 | +MODULE_DESCRIPTION("xHCI sideband driver for secondary interrupter management"); | ||
495 | +MODULE_LICENSE("GPL"); | ||
439 | diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h | 496 | diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h |
440 | index XXXXXXX..XXXXXXX 100644 | 497 | index XXXXXXX..XXXXXXX 100644 |
441 | --- a/drivers/usb/host/xhci.h | 498 | --- a/drivers/usb/host/xhci.h |
442 | +++ b/drivers/usb/host/xhci.h | 499 | +++ b/drivers/usb/host/xhci.h |
443 | @@ -XXX,XX +XXX,XX @@ struct xhci_virt_ep { | 500 | @@ -XXX,XX +XXX,XX @@ struct xhci_virt_ep { |
444 | int next_frame_id; | 501 | int next_frame_id; |
445 | /* Use new Isoch TRB layout needed for extended TBC support */ | 502 | /* Use new Isoch TRB layout needed for extended TBC support */ |
446 | bool use_extended_tbc; | 503 | bool use_extended_tbc; |
447 | + /* set if this endpoint is controlled via sideband access*/ | 504 | + /* set if this endpoint is controlled via sideband access*/ |
448 | + struct xhci_sideband *sideband; | 505 | + struct xhci_sideband *sideband; |
449 | }; | 506 | }; |
450 | 507 | ||
451 | enum xhci_overhead_type { | 508 | enum xhci_overhead_type { |
452 | @@ -XXX,XX +XXX,XX @@ struct xhci_virt_device { | 509 | @@ -XXX,XX +XXX,XX @@ struct xhci_virt_device { |
453 | u16 current_mel; | 510 | u16 current_mel; |
454 | /* Used for the debugfs interfaces. */ | 511 | /* Used for the debugfs interfaces. */ |
455 | void *debugfs_private; | 512 | void *debugfs_private; |
456 | + /* set if this device is registered for sideband access */ | 513 | + /* set if this endpoint is controlled via sideband access*/ |
457 | + struct xhci_sideband *sideband; | 514 | + struct xhci_sideband *sideband; |
458 | }; | 515 | }; |
459 | 516 | ||
460 | /* | 517 | /* |
461 | diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h | 518 | diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h |
462 | new file mode 100644 | 519 | new file mode 100644 |
... | ... | ||
466 | @@ -XXX,XX +XXX,XX @@ | 523 | @@ -XXX,XX +XXX,XX @@ |
467 | +/* SPDX-License-Identifier: GPL-2.0 */ | 524 | +/* SPDX-License-Identifier: GPL-2.0 */ |
468 | +/* | 525 | +/* |
469 | + * xHCI host controller sideband support | 526 | + * xHCI host controller sideband support |
470 | + * | 527 | + * |
471 | + * Copyright (c) 2023, Intel Corporation. | 528 | + * Copyright (c) 2023-2025, Intel Corporation. |
472 | + * | 529 | + * |
473 | + * Author: Mathias Nyman <mathias.nyman@linux.intel.com> | 530 | + * Author: Mathias Nyman <mathias.nyman@linux.intel.com> |
474 | + */ | 531 | + */ |
475 | + | ||
476 | +#ifndef __LINUX_XHCI_SIDEBAND_H | 532 | +#ifndef __LINUX_XHCI_SIDEBAND_H |
477 | +#define __LINUX_XHCI_SIDEBAND_H | 533 | +#define __LINUX_XHCI_SIDEBAND_H |
478 | + | 534 | + |
479 | +#include <linux/scatterlist.h> | 535 | +#include <linux/scatterlist.h> |
480 | +#include <linux/usb.h> | 536 | +#include <linux/usb.h> |
481 | + | 537 | + |
482 | +#define EP_CTX_PER_DEV 31 /* FIMXME defined twice, from xhci.h */ | 538 | +#define EP_CTX_PER_DEV 31 /* FIXME defined twice, from xhci.h */ |
483 | + | 539 | + |
484 | +struct xhci_sideband; | 540 | +struct xhci_sideband; |
541 | + | ||
542 | +enum xhci_sideband_type { | ||
543 | + XHCI_SIDEBAND_AUDIO, | ||
544 | + XHCI_SIDEBAND_VENDOR, | ||
545 | +}; | ||
485 | + | 546 | + |
486 | +/** | 547 | +/** |
487 | + * struct xhci_sideband - representation of a sideband accessed usb device. | 548 | + * struct xhci_sideband - representation of a sideband accessed usb device. |
488 | + * @xhci: The xhci host controller the usb device is connected to | 549 | + * @xhci: The xhci host controller the usb device is connected to |
489 | + * @vdev: the usb device accessed via sideband | 550 | + * @vdev: the usb device accessed via sideband |
490 | + * @eps: array of endpoints controlled via sideband | 551 | + * @eps: array of endpoints controlled via sideband |
491 | + * @ir: event handling and buffer for sideband accessed device | 552 | + * @ir: event handling and buffer for sideband accessed device |
553 | + * @type: xHCI sideband type | ||
554 | + * @mutex: mutex for sideband operations | ||
555 | + * @intf: USB sideband client interface | ||
492 | + * | 556 | + * |
493 | + * FIXME usb device accessed via sideband Keeping track of sideband accessed usb devices. | 557 | + * FIXME usb device accessed via sideband Keeping track of sideband accessed usb devices. |
494 | + */ | 558 | + */ |
495 | + | ||
496 | +struct xhci_sideband { | 559 | +struct xhci_sideband { |
497 | + struct xhci_hcd *xhci; | 560 | + struct xhci_hcd *xhci; |
498 | + struct xhci_virt_device *vdev; | 561 | + struct xhci_virt_device *vdev; |
499 | + struct xhci_virt_ep *eps[EP_CTX_PER_DEV]; | 562 | + struct xhci_virt_ep *eps[EP_CTX_PER_DEV]; |
500 | + struct xhci_interrupter *ir; | 563 | + struct xhci_interrupter *ir; |
564 | + enum xhci_sideband_type type; | ||
565 | + | ||
566 | + /* Synchronizing xHCI sideband operations with client drivers operations */ | ||
567 | + struct mutex mutex; | ||
568 | + | ||
569 | + struct usb_interface *intf; | ||
501 | +}; | 570 | +}; |
502 | + | 571 | + |
503 | +struct xhci_sideband * | 572 | +struct xhci_sideband * |
504 | +xhci_sideband_register(struct usb_device *udev); | 573 | +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type); |
505 | +void | 574 | +void |
506 | +xhci_sideband_unregister(struct xhci_sideband *sb); | 575 | +xhci_sideband_unregister(struct xhci_sideband *sb); |
507 | +int | 576 | +int |
508 | +xhci_sideband_add_endpoint(struct xhci_sideband *sb, | 577 | +xhci_sideband_add_endpoint(struct xhci_sideband *sb, |
509 | + struct usb_host_endpoint *host_ep); | 578 | + struct usb_host_endpoint *host_ep); |
... | ... | ||
516 | +struct sg_table * | 585 | +struct sg_table * |
517 | +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, | 586 | +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, |
518 | + struct usb_host_endpoint *host_ep); | 587 | + struct usb_host_endpoint *host_ep); |
519 | +struct sg_table * | 588 | +struct sg_table * |
520 | +xhci_sideband_get_event_buffer(struct xhci_sideband *sb); | 589 | +xhci_sideband_get_event_buffer(struct xhci_sideband *sb); |
521 | + | 590 | +int |
522 | +int | 591 | +xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, |
523 | +xhci_sideband_create_interrupter(struct xhci_sideband *sb); | 592 | + bool ip_autoclear, u32 imod_interval); |
524 | + | ||
525 | +void | 593 | +void |
526 | +xhci_sideband_remove_interrupter(struct xhci_sideband *sb); | 594 | +xhci_sideband_remove_interrupter(struct xhci_sideband *sb); |
527 | + | ||
528 | +int | 595 | +int |
529 | +xhci_sideband_interrupter_id(struct xhci_sideband *sb); | 596 | +xhci_sideband_interrupter_id(struct xhci_sideband *sb); |
530 | + | ||
531 | +#endif /* __LINUX_XHCI_SIDEBAND_H */ | 597 | +#endif /* __LINUX_XHCI_SIDEBAND_H */ |
532 | + | diff view generated by jsdifflib |
1 | As part of xHCI bus suspend, the XHCI is halted. However, if there are | 1 | As part of xHCI bus suspend, the xHCI is halted. However, if there are |
---|---|---|---|
2 | pending events in the secondary event ring, it is observed that the xHCI | 2 | pending events in the secondary event ring, it is observed that the xHCI |
3 | controller stops responding to further commands upon host or device | 3 | controller stops responding to further commands upon host or device |
4 | initiated bus resume. Iterate through all pending events and update the | 4 | initiated bus resume. Iterate through all pending events and update the |
5 | dequeue pointer to the beginning of the event ring. | 5 | dequeue pointer to the beginning of the event ring. |
6 | 6 | ||
7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
8 | --- | 8 | --- |
9 | drivers/usb/host/xhci-mem.c | 13 +++++---- | 9 | drivers/usb/host/xhci-mem.c | 7 +++++- |
10 | drivers/usb/host/xhci-ring.c | 51 +++++++++++++++++++++++++++++++++++- | 10 | drivers/usb/host/xhci-ring.c | 47 ++++++++++++++++++++++++++++++------ |
11 | drivers/usb/host/xhci.c | 2 +- | 11 | drivers/usb/host/xhci.c | 2 +- |
12 | drivers/usb/host/xhci.h | 6 +++++ | 12 | drivers/usb/host/xhci.h | 7 ++++++ |
13 | 4 files changed, 63 insertions(+), 9 deletions(-) | 13 | 4 files changed, 54 insertions(+), 9 deletions(-) |
14 | 14 | ||
15 | diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c | 15 | diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c |
16 | index XXXXXXX..XXXXXXX 100644 | 16 | index XXXXXXX..XXXXXXX 100644 |
17 | --- a/drivers/usb/host/xhci-mem.c | 17 | --- a/drivers/usb/host/xhci-mem.c |
18 | +++ b/drivers/usb/host/xhci-mem.c | 18 | +++ b/drivers/usb/host/xhci-mem.c |
19 | @@ -XXX,XX +XXX,XX @@ xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) | 19 | @@ -XXX,XX +XXX,XX @@ xhci_remove_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) |
20 | tmp &= ERST_SIZE_MASK; | 20 | tmp &= ERST_SIZE_MASK; |
21 | writel(tmp, &ir->ir_set->erst_size); | 21 | writel(tmp, &ir->ir_set->erst_size); |
22 | 22 | ||
23 | - tmp64 = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); | 23 | - xhci_write_64(xhci, ERST_EHB, &ir->ir_set->erst_dequeue); |
24 | - tmp64 &= (u64) ERST_PTR_MASK; | 24 | + xhci_update_erst_dequeue(xhci, ir, true); |
25 | - xhci_write_64(xhci, tmp64, &ir->ir_set->erst_dequeue); | ||
26 | + xhci_update_erst_dequeue(xhci, ir, ir->event_ring->first_seg->trbs); | ||
27 | } | 25 | } |
28 | } | 26 | } |
29 | 27 | ||
30 | @@ -XXX,XX +XXX,XX @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup | 28 | @@ -XXX,XX +XXX,XX @@ void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrup |
31 | if (!ir || !ir->intr_num || ir->intr_num >= xhci->max_interrupters) | 29 | return; |
32 | xhci_dbg(xhci, "Invalid secondary interrupter, can't remove\n"); | 30 | } |
33 | 31 | ||
34 | - /* fixme, should we check xhci->interrupter[intr_num] == ir */ | ||
35 | - /* fixme locking */ | ||
36 | - | ||
37 | spin_lock_irq(&xhci->lock); | ||
38 | - | ||
39 | + /* | 32 | + /* |
40 | + * Cleanup secondary interrupter to ensure there are no pending events. | 33 | + * Cleanup secondary interrupter to ensure there are no pending events. |
41 | + * This also updates event ring dequeue pointer back to the start. | 34 | + * This also updates event ring dequeue pointer back to the start. |
42 | + */ | 35 | + */ |
43 | + xhci_skip_sec_intr_events(xhci, ir->event_ring, ir); | 36 | + xhci_skip_sec_intr_events(xhci, ir->event_ring, ir); |
... | ... | ||
46 | xhci_remove_interrupter(xhci, ir); | 39 | xhci_remove_interrupter(xhci, ir); |
47 | diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c | 40 | diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c |
48 | index XXXXXXX..XXXXXXX 100644 | 41 | index XXXXXXX..XXXXXXX 100644 |
49 | --- a/drivers/usb/host/xhci-ring.c | 42 | --- a/drivers/usb/host/xhci-ring.c |
50 | +++ b/drivers/usb/host/xhci-ring.c | 43 | +++ b/drivers/usb/host/xhci-ring.c |
51 | @@ -XXX,XX +XXX,XX @@ static int xhci_handle_event(struct xhci_hcd *xhci, struct xhci_interrupter *ir) | 44 | @@ -XXX,XX +XXX,XX @@ static int xhci_handle_event_trb(struct xhci_hcd *xhci, struct xhci_interrupter |
52 | * - When all events have finished | 45 | * - When all events have finished |
53 | * - To avoid "Event Ring Full Error" condition | 46 | * - To avoid "Event Ring Full Error" condition |
54 | */ | 47 | */ |
55 | -static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, | 48 | -static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, |
49 | - struct xhci_interrupter *ir, | ||
50 | - bool clear_ehb) | ||
56 | +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, | 51 | +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, |
57 | struct xhci_interrupter *ir, | 52 | + struct xhci_interrupter *ir, |
58 | union xhci_trb *event_ring_deq, | 53 | + bool clear_ehb) |
59 | bool clear_ehb) | 54 | { |
60 | @@ -XXX,XX +XXX,XX @@ static void xhci_update_erst_dequeue(struct xhci_hcd *xhci, | 55 | u64 temp_64; |
61 | xhci_write_64(xhci, temp_64, &ir->ir_set->erst_dequeue); | 56 | dma_addr_t deq; |
57 | @@ -XXX,XX +XXX,XX @@ static void xhci_clear_interrupt_pending(struct xhci_interrupter *ir) | ||
58 | * Handle all OS-owned events on an interrupter event ring. It may drop | ||
59 | * and reaquire xhci->lock between event processing. | ||
60 | */ | ||
61 | -static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir) | ||
62 | +static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir, | ||
63 | + bool skip_events) | ||
64 | { | ||
65 | int event_loop = 0; | ||
66 | - int err; | ||
67 | + int err = 0; | ||
68 | u64 temp; | ||
69 | |||
70 | xhci_clear_interrupt_pending(ir); | ||
71 | @@ -XXX,XX +XXX,XX @@ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir | ||
72 | |||
73 | /* Process all OS owned event TRBs on this event ring */ | ||
74 | while (unhandled_event_trb(ir->event_ring)) { | ||
75 | - err = xhci_handle_event_trb(xhci, ir, ir->event_ring->dequeue); | ||
76 | + if (!skip_events) | ||
77 | + err = xhci_handle_event_trb(xhci, ir, ir->event_ring->dequeue); | ||
78 | |||
79 | /* | ||
80 | * If half a segment of events have been handled in one go then | ||
81 | @@ -XXX,XX +XXX,XX @@ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir | ||
82 | return 0; | ||
62 | } | 83 | } |
63 | 84 | ||
64 | +/* | 85 | +/* |
65 | + * Move the event ring dequeue pointer to skip events kept in the secondary | 86 | + * Move the event ring dequeue pointer to skip events kept in the secondary |
66 | + * event ring. This is used to ensure that pending events in the ring are | 87 | + * event ring. This is used to ensure that pending events in the ring are |
67 | + * acknowledged, so the XHCI HCD can properly enter suspend/resume. The | 88 | + * acknowledged, so the xHCI HCD can properly enter suspend/resume. The |
68 | + * secondary ring is typically maintained by an external component. | 89 | + * secondary ring is typically maintained by an external component. |
69 | + */ | 90 | + */ |
70 | +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, | 91 | +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, |
71 | + struct xhci_ring *ring, struct xhci_interrupter *ir) | 92 | + struct xhci_ring *ring, struct xhci_interrupter *ir) |
72 | +{ | 93 | +{ |
73 | + union xhci_trb *erdp_trb, *current_trb; | 94 | + union xhci_trb *current_trb; |
74 | + struct xhci_segment *seg; | ||
75 | + u64 erdp_reg; | 95 | + u64 erdp_reg; |
76 | + u32 iman_reg; | ||
77 | + dma_addr_t deq; | 96 | + dma_addr_t deq; |
78 | + unsigned long segment_offset; | ||
79 | + | 97 | + |
80 | + /* disable irq, ack pending interrupt and ack all pending events */ | 98 | + /* disable irq, ack pending interrupt and ack all pending events */ |
81 | + xhci_disable_interrupter(ir); | 99 | + xhci_disable_interrupter(ir); |
82 | + iman_reg = readl_relaxed(&ir->ir_set->irq_pending); | ||
83 | + if (iman_reg & IMAN_IP) | ||
84 | + writel_relaxed(iman_reg, &ir->ir_set->irq_pending); | ||
85 | + | 100 | + |
86 | + /* last acked event trb is in erdp reg */ | 101 | + /* last acked event trb is in erdp reg */ |
87 | + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); | 102 | + erdp_reg = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); |
88 | + deq = (dma_addr_t)(erdp_reg & ~ERST_PTR_MASK); | 103 | + deq = (dma_addr_t)(erdp_reg & ERST_PTR_MASK); |
89 | + if (!deq) { | 104 | + if (!deq) { |
90 | + xhci_dbg(xhci, "event ring handling not required\n"); | 105 | + xhci_err(xhci, "event ring handling not required\n"); |
91 | + return; | 106 | + return; |
92 | + } | 107 | + } |
93 | + | 108 | + |
94 | + seg = ring->first_seg; | 109 | + current_trb = ir->event_ring->dequeue; |
95 | + segment_offset = deq - seg->dma; | ||
96 | + | ||
97 | + erdp_trb = current_trb = ir->event_ring->dequeue; | ||
98 | + /* read cycle state of the last acked trb to find out CCS */ | 110 | + /* read cycle state of the last acked trb to find out CCS */ |
99 | + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; | 111 | + ring->cycle_state = le32_to_cpu(current_trb->event_cmd.flags) & TRB_CYCLE; |
100 | + | 112 | + |
101 | + while (1) { | 113 | + xhci_handle_events(xhci, ir, true); |
102 | + inc_deq(xhci, ir->event_ring); | ||
103 | + erdp_trb = ir->event_ring->dequeue; | ||
104 | + /* cycle state transition */ | ||
105 | + if ((le32_to_cpu(erdp_trb->event_cmd.flags) & TRB_CYCLE) != | ||
106 | + ring->cycle_state) | ||
107 | + break; | ||
108 | + } | ||
109 | + | ||
110 | + xhci_update_erst_dequeue(xhci, ir, current_trb); | ||
111 | +} | 114 | +} |
112 | + | 115 | + |
113 | /* | 116 | /* |
114 | * xHCI spec says we can get an interrupt, and if the HC has an error condition, | 117 | * xHCI spec says we can get an interrupt, and if the HC has an error condition, |
115 | * we might get bad data out of the event ring. Section 4.10.2.7 has a list of | 118 | * we might get bad data out of the event ring. Section 4.10.2.7 has a list of |
119 | @@ -XXX,XX +XXX,XX @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) | ||
120 | writel(status, &xhci->op_regs->status); | ||
121 | |||
122 | /* This is the handler of the primary interrupter */ | ||
123 | - xhci_handle_events(xhci, xhci->interrupters[0]); | ||
124 | + xhci_handle_events(xhci, xhci->interrupters[0], false); | ||
125 | out: | ||
126 | spin_unlock(&xhci->lock); | ||
127 | |||
116 | diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c | 128 | diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c |
117 | index XXXXXXX..XXXXXXX 100644 | 129 | index XXXXXXX..XXXXXXX 100644 |
118 | --- a/drivers/usb/host/xhci.c | 130 | --- a/drivers/usb/host/xhci.c |
119 | +++ b/drivers/usb/host/xhci.c | 131 | +++ b/drivers/usb/host/xhci.c |
120 | @@ -XXX,XX +XXX,XX @@ static int xhci_enable_interrupter(struct xhci_interrupter *ir) | 132 | @@ -XXX,XX +XXX,XX @@ static int xhci_enable_interrupter(struct xhci_interrupter *ir) |
... | ... | ||
128 | 140 | ||
129 | diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h | 141 | diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h |
130 | index XXXXXXX..XXXXXXX 100644 | 142 | index XXXXXXX..XXXXXXX 100644 |
131 | --- a/drivers/usb/host/xhci.h | 143 | --- a/drivers/usb/host/xhci.h |
132 | +++ b/drivers/usb/host/xhci.h | 144 | +++ b/drivers/usb/host/xhci.h |
133 | @@ -XXX,XX +XXX,XX @@ void xhci_free_container_ctx(struct xhci_hcd *xhci, | 145 | @@ -XXX,XX +XXX,XX @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, |
134 | struct xhci_interrupter *xhci_create_secondary_interrupter(struct usb_hcd *hcd); | 146 | u32 imod_interval); |
135 | void xhci_remove_secondary_interrupter(struct usb_hcd | 147 | void xhci_remove_secondary_interrupter(struct usb_hcd |
136 | *hcd, struct xhci_interrupter *ir); | 148 | *hcd, struct xhci_interrupter *ir); |
137 | +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, | 149 | +void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, |
138 | + struct xhci_ring *ring, struct xhci_interrupter *ir); | 150 | + struct xhci_ring *ring, |
139 | +int xhci_disable_interrupter(struct xhci_interrupter *ir); | 151 | + struct xhci_interrupter *ir); |
140 | 152 | ||
141 | /* xHCI host controller glue */ | 153 | /* xHCI host controller glue */ |
142 | typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); | 154 | typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); |
143 | @@ -XXX,XX +XXX,XX @@ void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring); | 155 | @@ -XXX,XX +XXX,XX @@ int xhci_alloc_tt_info(struct xhci_hcd *xhci, |
144 | unsigned int count_trbs(u64 addr, u64 len); | 156 | struct usb_tt *tt, gfp_t mem_flags); |
157 | int xhci_set_interrupter_moderation(struct xhci_interrupter *ir, | ||
158 | u32 imod_interval); | ||
159 | +int xhci_disable_interrupter(struct xhci_interrupter *ir); | ||
160 | |||
161 | /* xHCI ring, segment, TRB, and TD functions */ | ||
162 | dma_addr_t xhci_trb_virt_to_dma(struct xhci_segment *seg, union xhci_trb *trb); | ||
163 | @@ -XXX,XX +XXX,XX @@ unsigned int count_trbs(u64 addr, u64 len); | ||
145 | int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, | 164 | int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, |
146 | int suspend, gfp_t gfp_flags); | 165 | int suspend, gfp_t gfp_flags); |
166 | void xhci_process_cancelled_tds(struct xhci_virt_ep *ep); | ||
147 | +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, | 167 | +void xhci_update_erst_dequeue(struct xhci_hcd *xhci, |
148 | + struct xhci_interrupter *ir, | 168 | + struct xhci_interrupter *ir, |
149 | + union xhci_trb *event_ring_deq); | 169 | + bool clear_ehb); |
150 | 170 | ||
151 | /* xHCI roothub code */ | 171 | /* xHCI roothub code */ |
152 | void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, | 172 | void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, | diff view generated by jsdifflib |
1 | Some clients may operate only on a specific XHCI interrupter instance. | 1 | Some clients may operate only on a specific XHCI interrupter instance. |
---|---|---|---|
2 | Allow for the associated class driver to request for the interrupter that | 2 | Allow for the associated class driver to request for the interrupter that |
3 | it requires. | 3 | it requires. |
4 | 4 | ||
5 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 5 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
6 | --- | 6 | --- |
7 | drivers/usb/host/xhci-mem.c | 30 ++++++++++++++++-------------- | 7 | drivers/usb/host/xhci-mem.c | 24 ++++++++++++++---------- |
8 | drivers/usb/host/xhci-sideband.c | 5 +++-- | 8 | drivers/usb/host/xhci-sideband.c | 5 +++-- |
9 | drivers/usb/host/xhci.h | 3 ++- | 9 | drivers/usb/host/xhci.h | 2 +- |
10 | include/linux/usb/xhci-sideband.h | 3 ++- | 10 | include/linux/usb/xhci-sideband.h | 2 +- |
11 | 4 files changed, 23 insertions(+), 18 deletions(-) | 11 | 4 files changed, 19 insertions(+), 14 deletions(-) |
12 | 12 | ||
13 | diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c | 13 | diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c |
14 | index XXXXXXX..XXXXXXX 100644 | 14 | index XXXXXXX..XXXXXXX 100644 |
15 | --- a/drivers/usb/host/xhci-mem.c | 15 | --- a/drivers/usb/host/xhci-mem.c |
16 | +++ b/drivers/usb/host/xhci-mem.c | 16 | +++ b/drivers/usb/host/xhci-mem.c |
17 | @@ -XXX,XX +XXX,XX @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, | 17 | @@ -XXX,XX +XXX,XX @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, |
18 | } | ||
19 | 18 | ||
20 | struct xhci_interrupter * | 19 | struct xhci_interrupter * |
21 | -xhci_create_secondary_interrupter(struct usb_hcd *hcd) | 20 | xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, |
22 | +xhci_create_secondary_interrupter(struct usb_hcd *hcd, int intr_num) | 21 | - u32 imod_interval) |
22 | + u32 imod_interval, unsigned int intr_num) | ||
23 | { | 23 | { |
24 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | 24 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); |
25 | struct xhci_interrupter *ir; | 25 | struct xhci_interrupter *ir; |
26 | @@ -XXX,XX +XXX,XX @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd) | 26 | unsigned int i; |
27 | int err = -ENOSPC; | ||
28 | |||
29 | - if (!xhci->interrupters || xhci->max_interrupters <= 1) | ||
30 | + if (!xhci->interrupters || xhci->max_interrupters <= 1 || | ||
31 | + intr_num >= xhci->max_interrupters) | ||
27 | return NULL; | 32 | return NULL; |
33 | |||
34 | ir = xhci_alloc_interrupter(xhci, segs, GFP_KERNEL); | ||
35 | @@ -XXX,XX +XXX,XX @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, | ||
36 | return NULL; | ||
28 | 37 | ||
29 | spin_lock_irq(&xhci->lock); | 38 | spin_lock_irq(&xhci->lock); |
30 | - | 39 | - |
31 | - /* Find available secondary interrupter, interrupter 0 is reserved for primary */ | 40 | - /* Find available secondary interrupter, interrupter 0 is reserved for primary */ |
32 | + /* Find available secondary interrupter, interrupter 0 is reserverd for primary */ | 41 | - for (i = 1; i < xhci->max_interrupters; i++) { |
33 | for (i = 1; i < xhci->max_interrupters; i++) { | ||
34 | - if (xhci->interrupters[i] == NULL) { | 42 | - if (xhci->interrupters[i] == NULL) { |
35 | - err = xhci_add_interrupter(xhci, ir, i); | 43 | - err = xhci_add_interrupter(xhci, ir, i); |
36 | - break; | 44 | - break; |
37 | + if ((intr_num > 0 && i == intr_num) || intr_num <= 0) { | 45 | + if (!intr_num) { |
38 | + if (xhci->interrupters[i] == NULL) { | 46 | + /* Find available secondary interrupter, interrupter 0 is reserved for primary */ |
47 | + for (i = 1; i < xhci->max_interrupters; i++) { | ||
48 | + if (!xhci->interrupters[i]) { | ||
39 | + err = xhci_add_interrupter(xhci, ir, i); | 49 | + err = xhci_add_interrupter(xhci, ir, i); |
40 | + if (err) { | ||
41 | + spin_unlock_irq(&xhci->lock); | ||
42 | + goto free_ir; | ||
43 | + } | ||
44 | + break; | 50 | + break; |
45 | + } | 51 | + } |
46 | } | 52 | } |
53 | + } else { | ||
54 | + if (!xhci->interrupters[intr_num]) | ||
55 | + err = xhci_add_interrupter(xhci, ir, intr_num); | ||
47 | } | 56 | } |
48 | - | 57 | - |
49 | spin_unlock_irq(&xhci->lock); | 58 | spin_unlock_irq(&xhci->lock); |
50 | 59 | ||
51 | - if (err) { | 60 | if (err) { |
52 | - xhci_warn(xhci, "Failed to add secondary interrupter, max interrupters %d\n", | 61 | @@ -XXX,XX +XXX,XX @@ xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, |
53 | - xhci->max_interrupters); | 62 | i, imod_interval); |
54 | - xhci_free_interrupter(xhci, ir); | 63 | |
55 | - return NULL; | ||
56 | - } | ||
57 | - | ||
58 | xhci_dbg(xhci, "Add secondary interrupter %d, max interrupters %d\n", | 64 | xhci_dbg(xhci, "Add secondary interrupter %d, max interrupters %d\n", |
59 | i, xhci->max_interrupters); | 65 | - i, xhci->max_interrupters); |
66 | + ir->intr_num, xhci->max_interrupters); | ||
60 | 67 | ||
61 | return ir; | 68 | return ir; |
62 | + | ||
63 | +free_ir: | ||
64 | + xhci_free_interrupter(xhci, ir); | ||
65 | + | ||
66 | + return NULL; | ||
67 | } | 69 | } |
68 | EXPORT_SYMBOL_GPL(xhci_create_secondary_interrupter); | ||
69 | |||
70 | diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c | 70 | diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c |
71 | index XXXXXXX..XXXXXXX 100644 | 71 | index XXXXXXX..XXXXXXX 100644 |
72 | --- a/drivers/usb/host/xhci-sideband.c | 72 | --- a/drivers/usb/host/xhci-sideband.c |
73 | +++ b/drivers/usb/host/xhci-sideband.c | 73 | +++ b/drivers/usb/host/xhci-sideband.c |
74 | @@ -XXX,XX +XXX,XX @@ EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); | 74 | @@ -XXX,XX +XXX,XX @@ EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); |
75 | * Returns 0 on success, negative error otherwise | ||
76 | */ | 75 | */ |
77 | int | 76 | int |
78 | -xhci_sideband_create_interrupter(struct xhci_sideband *sb) | 77 | xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, |
79 | +xhci_sideband_create_interrupter(struct xhci_sideband *sb, int intr_num) | 78 | - bool ip_autoclear, u32 imod_interval) |
79 | + bool ip_autoclear, u32 imod_interval, int intr_num) | ||
80 | { | 80 | { |
81 | if (sb->ir) | 81 | int ret = 0; |
82 | return -EBUSY; | 82 | |
83 | 83 | @@ -XXX,XX +XXX,XX @@ xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, | |
84 | - sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci)); | 84 | } |
85 | + sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci), | 85 | |
86 | + intr_num); | 86 | sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci), |
87 | if (!sb->ir) | 87 | - num_seg, imod_interval); |
88 | return -ENOMEM; | 88 | + num_seg, imod_interval, |
89 | 89 | + intr_num); | |
90 | if (!sb->ir) { | ||
91 | ret = -ENOMEM; | ||
92 | goto out; | ||
90 | diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h | 93 | diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h |
91 | index XXXXXXX..XXXXXXX 100644 | 94 | index XXXXXXX..XXXXXXX 100644 |
92 | --- a/drivers/usb/host/xhci.h | 95 | --- a/drivers/usb/host/xhci.h |
93 | +++ b/drivers/usb/host/xhci.h | 96 | +++ b/drivers/usb/host/xhci.h |
94 | @@ -XXX,XX +XXX,XX @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, | 97 | @@ -XXX,XX +XXX,XX @@ void xhci_free_container_ctx(struct xhci_hcd *xhci, |
95 | int type, gfp_t flags); | ||
96 | void xhci_free_container_ctx(struct xhci_hcd *xhci, | ||
97 | struct xhci_container_ctx *ctx); | 98 | struct xhci_container_ctx *ctx); |
98 | -struct xhci_interrupter *xhci_create_secondary_interrupter(struct usb_hcd *hcd); | 99 | struct xhci_interrupter * |
99 | +struct xhci_interrupter *xhci_create_secondary_interrupter(struct usb_hcd *hcd, | 100 | xhci_create_secondary_interrupter(struct usb_hcd *hcd, unsigned int segs, |
100 | + int intr_num); | 101 | - u32 imod_interval); |
102 | + u32 imod_interval, unsigned int intr_num); | ||
101 | void xhci_remove_secondary_interrupter(struct usb_hcd | 103 | void xhci_remove_secondary_interrupter(struct usb_hcd |
102 | *hcd, struct xhci_interrupter *ir); | 104 | *hcd, struct xhci_interrupter *ir); |
103 | void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, | 105 | void xhci_skip_sec_intr_events(struct xhci_hcd *xhci, |
104 | diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h | 106 | diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h |
105 | index XXXXXXX..XXXXXXX 100644 | 107 | index XXXXXXX..XXXXXXX 100644 |
106 | --- a/include/linux/usb/xhci-sideband.h | 108 | --- a/include/linux/usb/xhci-sideband.h |
107 | +++ b/include/linux/usb/xhci-sideband.h | 109 | +++ b/include/linux/usb/xhci-sideband.h |
108 | @@ -XXX,XX +XXX,XX @@ struct sg_table * | 110 | @@ -XXX,XX +XXX,XX @@ struct sg_table * |
109 | xhci_sideband_get_event_buffer(struct xhci_sideband *sb); | 111 | xhci_sideband_get_event_buffer(struct xhci_sideband *sb); |
110 | |||
111 | int | 112 | int |
112 | -xhci_sideband_create_interrupter(struct xhci_sideband *sb); | 113 | xhci_sideband_create_interrupter(struct xhci_sideband *sb, int num_seg, |
113 | +xhci_sideband_create_interrupter(struct xhci_sideband *sb, | 114 | - bool ip_autoclear, u32 imod_interval); |
114 | + int intr_num); | 115 | + bool ip_autoclear, u32 imod_interval, int intr_num); |
115 | |||
116 | void | 116 | void |
117 | xhci_sideband_remove_interrupter(struct xhci_sideband *sb); | 117 | xhci_sideband_remove_interrupter(struct xhci_sideband *sb); |
118 | int | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
17 | &xhci->imod_interval); | 17 | &xhci->imod_interval); |
18 | + device_property_read_u16(tmpdev, "num-hc-interrupters", | 18 | + device_property_read_u16(tmpdev, "num-hc-interrupters", |
19 | + &xhci->max_interrupters); | 19 | + &xhci->max_interrupters); |
20 | } | 20 | } |
21 | 21 | ||
22 | hcd->usb_phy = devm_usb_get_phy_by_phandle(sysdev, "usb-phy", 0); | 22 | /* | diff view generated by jsdifflib |
1 | The USB backend should know about which sound resources are being shared | 1 | In the case of handling a USB bus reset, the xhci_discover_or_reset_device |
---|---|---|---|
2 | between the ASoC and USB SND paths. This can be utilized to properly | 2 | can run without first notifying the xHCI sideband client driver to stop or |
3 | select and maintain the offloading devices. | 3 | prevent the use of the transfer ring. It was seen that when a bus reset |
4 | situation happened, the USB offload driver was attempting to fetch the xHCI | ||
5 | transfer ring information, which was already freed. | ||
4 | 6 | ||
5 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
6 | --- | 8 | --- |
7 | sound/soc/qcom/qdsp6/q6usb.c | 16 ++++++++++++++++ | 9 | drivers/usb/host/xhci-sideband.c | 29 ++++++++++++++++++++++++++++- |
8 | 1 file changed, 16 insertions(+) | 10 | drivers/usb/host/xhci.c | 3 +++ |
11 | include/linux/usb/xhci-sideband.h | 30 +++++++++++++++++++++++++++++- | ||
12 | 3 files changed, 60 insertions(+), 2 deletions(-) | ||
9 | 13 | ||
10 | diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c | 14 | diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c |
11 | index XXXXXXX..XXXXXXX 100644 | 15 | index XXXXXXX..XXXXXXX 100644 |
12 | --- a/sound/soc/qcom/qdsp6/q6usb.c | 16 | --- a/drivers/usb/host/xhci-sideband.c |
13 | +++ b/sound/soc/qcom/qdsp6/q6usb.c | 17 | +++ b/drivers/usb/host/xhci-sideband.c |
18 | @@ -XXX,XX +XXX,XX @@ __xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *e | ||
19 | |||
20 | /* sideband api functions */ | ||
21 | |||
22 | +/** | ||
23 | + * xhci_sideband_notify_ep_ring_free - notify client of xfer ring free | ||
24 | + * @sb: sideband instance for this usb device | ||
25 | + * @ep_index: usb endpoint index | ||
26 | + * | ||
27 | + * Notifies the xHCI sideband client driver of a xHCI transfer ring free | ||
28 | + * routine. This will allow for the client to ensure that all transfers | ||
29 | + * are completed. | ||
30 | + * | ||
31 | + * The callback should be synchronous, as the ring free happens after. | ||
32 | + */ | ||
33 | +void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb, | ||
34 | + unsigned int ep_index) | ||
35 | +{ | ||
36 | + struct xhci_sideband_event evt; | ||
37 | + | ||
38 | + evt.type = XHCI_SIDEBAND_XFER_RING_FREE; | ||
39 | + evt.evt_data = &ep_index; | ||
40 | + | ||
41 | + if (sb->notify_client) | ||
42 | + sb->notify_client(sb->intf, &evt); | ||
43 | +} | ||
44 | +EXPORT_SYMBOL_GPL(xhci_sideband_notify_ep_ring_free); | ||
45 | + | ||
46 | /** | ||
47 | * xhci_sideband_add_endpoint - add endpoint to sideband access list | ||
48 | * @sb: sideband instance for this usb device | ||
49 | @@ -XXX,XX +XXX,XX @@ EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id); | ||
50 | * Return: pointer to a new xhci_sideband instance if successful. NULL otherwise. | ||
51 | */ | ||
52 | struct xhci_sideband * | ||
53 | -xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type) | ||
54 | +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type, | ||
55 | + int (*notify_client)(struct usb_interface *intf, | ||
56 | + struct xhci_sideband_event *evt)) | ||
57 | { | ||
58 | struct usb_device *udev = interface_to_usbdev(intf); | ||
59 | struct usb_hcd *hcd = bus_to_hcd(udev->bus); | ||
60 | @@ -XXX,XX +XXX,XX @@ xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type) | ||
61 | sb->vdev = vdev; | ||
62 | sb->intf = intf; | ||
63 | sb->type = type; | ||
64 | + sb->notify_client = notify_client; | ||
65 | vdev->sideband = sb; | ||
66 | |||
67 | spin_unlock_irq(&xhci->lock); | ||
68 | diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c | ||
69 | index XXXXXXX..XXXXXXX 100644 | ||
70 | --- a/drivers/usb/host/xhci.c | ||
71 | +++ b/drivers/usb/host/xhci.c | ||
14 | @@ -XXX,XX +XXX,XX @@ | 72 | @@ -XXX,XX +XXX,XX @@ |
15 | 73 | #include <linux/string_choices.h> | |
16 | #define SID_MASK 0xF | 74 | #include <linux/dmi.h> |
17 | 75 | #include <linux/dma-mapping.h> | |
18 | +struct q6usb_status { | 76 | +#include <linux/usb/xhci-sideband.h> |
19 | + unsigned int num_pcm; | 77 | |
20 | + unsigned int chip_index; | 78 | #include "xhci.h" |
21 | + unsigned int pcm_index; | 79 | #include "xhci-trace.h" |
80 | @@ -XXX,XX +XXX,XX @@ static int xhci_discover_or_reset_device(struct usb_hcd *hcd, | ||
81 | } | ||
82 | |||
83 | if (ep->ring) { | ||
84 | + if (ep->sideband) | ||
85 | + xhci_sideband_notify_ep_ring_free(ep->sideband, i); | ||
86 | xhci_debugfs_remove_endpoint(xhci, virt_dev, i); | ||
87 | xhci_free_endpoint_ring(xhci, virt_dev, i); | ||
88 | } | ||
89 | diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h | ||
90 | index XXXXXXX..XXXXXXX 100644 | ||
91 | --- a/include/linux/usb/xhci-sideband.h | ||
92 | +++ b/include/linux/usb/xhci-sideband.h | ||
93 | @@ -XXX,XX +XXX,XX @@ enum xhci_sideband_type { | ||
94 | XHCI_SIDEBAND_VENDOR, | ||
95 | }; | ||
96 | |||
97 | +enum xhci_sideband_notify_type { | ||
98 | + XHCI_SIDEBAND_XFER_RING_FREE, | ||
22 | +}; | 99 | +}; |
23 | + | 100 | + |
24 | struct q6usb_port_data { | 101 | +/** |
25 | struct q6afe_usb_cfg usb_cfg; | 102 | + * struct xhci_sideband_event - sideband event |
26 | struct snd_soc_usb *usb; | 103 | + * @type: notifier type |
27 | struct q6usb_offload priv; | 104 | + * @evt_data: event data |
28 | + unsigned long available_card_slot; | 105 | + */ |
29 | + struct q6usb_status status[SNDRV_CARDS]; | 106 | +struct xhci_sideband_event { |
30 | int active_idx; | 107 | + enum xhci_sideband_notify_type type; |
108 | + void *evt_data; | ||
109 | +}; | ||
110 | + | ||
111 | /** | ||
112 | * struct xhci_sideband - representation of a sideband accessed usb device. | ||
113 | * @xhci: The xhci host controller the usb device is connected to | ||
114 | @@ -XXX,XX +XXX,XX @@ enum xhci_sideband_type { | ||
115 | * @type: xHCI sideband type | ||
116 | * @mutex: mutex for sideband operations | ||
117 | * @intf: USB sideband client interface | ||
118 | + * @notify_client: callback for xHCI sideband sequences | ||
119 | * | ||
120 | * FIXME usb device accessed via sideband Keeping track of sideband accessed usb devices. | ||
121 | */ | ||
122 | @@ -XXX,XX +XXX,XX @@ struct xhci_sideband { | ||
123 | struct mutex mutex; | ||
124 | |||
125 | struct usb_interface *intf; | ||
126 | + int (*notify_client)(struct usb_interface *intf, | ||
127 | + struct xhci_sideband_event *evt); | ||
31 | }; | 128 | }; |
32 | 129 | ||
33 | @@ -XXX,XX +XXX,XX @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, | 130 | struct xhci_sideband * |
34 | if (connected) { | 131 | -xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type); |
35 | /* We only track the latest USB headset plugged in */ | 132 | +xhci_sideband_register(struct usb_interface *intf, enum xhci_sideband_type type, |
36 | data->active_idx = sdev->card_idx; | 133 | + int (*notify_client)(struct usb_interface *intf, |
134 | + struct xhci_sideband_event *evt)); | ||
135 | void | ||
136 | xhci_sideband_unregister(struct xhci_sideband *sb); | ||
137 | int | ||
138 | @@ -XXX,XX +XXX,XX @@ void | ||
139 | xhci_sideband_remove_interrupter(struct xhci_sideband *sb); | ||
140 | int | ||
141 | xhci_sideband_interrupter_id(struct xhci_sideband *sb); | ||
37 | + | 142 | + |
38 | + set_bit(sdev->card_idx, &data->available_card_slot); | 143 | +#if IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) |
39 | + data->status[sdev->card_idx].num_pcm = sdev->num_playback; | 144 | +void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb, |
40 | + data->status[sdev->card_idx].chip_index = sdev->chip_idx; | 145 | + unsigned int ep_index); |
41 | + } else { | 146 | +#else |
42 | + clear_bit(sdev->card_idx, &data->available_card_slot); | 147 | +static inline void xhci_sideband_notify_ep_ring_free(struct xhci_sideband *sb, |
43 | + data->status[sdev->card_idx].num_pcm = 0; | 148 | + unsigned int ep_index) |
44 | + data->status[sdev->card_idx].chip_index = 0; | 149 | +{ } |
45 | } | 150 | +#endif /* IS_ENABLED(CONFIG_USB_XHCI_SIDEBAND) */ |
46 | 151 | #endif /* __LINUX_XHCI_SIDEBAND_H */ | |
47 | return 0; | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
6 | Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com> | 6 | Acked-by: Thinh Nguyen <Thinh.Nguyen@synopsys.com> |
7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
8 | --- | 8 | --- |
9 | drivers/usb/dwc3/core.c | 12 ++++++++++++ | 9 | drivers/usb/dwc3/core.c | 12 ++++++++++++ |
10 | drivers/usb/dwc3/core.h | 2 ++ | 10 | drivers/usb/dwc3/core.h | 2 ++ |
11 | drivers/usb/dwc3/host.c | 5 ++++- | 11 | drivers/usb/dwc3/host.c | 3 +++ |
12 | 3 files changed, 18 insertions(+), 1 deletion(-) | 12 | 3 files changed, 17 insertions(+) |
13 | 13 | ||
14 | diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c | 14 | diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c |
15 | index XXXXXXX..XXXXXXX 100644 | 15 | index XXXXXXX..XXXXXXX 100644 |
16 | --- a/drivers/usb/dwc3/core.c | 16 | --- a/drivers/usb/dwc3/core.c |
17 | +++ b/drivers/usb/dwc3/core.c | 17 | +++ b/drivers/usb/dwc3/core.c |
18 | @@ -XXX,XX +XXX,XX @@ static void dwc3_get_properties(struct dwc3 *dwc) | 18 | @@ -XXX,XX +XXX,XX @@ static void dwc3_get_properties(struct dwc3 *dwc) |
19 | u8 tx_thr_num_pkt_prd = 0; | 19 | u8 tx_thr_num_pkt_prd = 0; |
20 | u8 tx_max_burst_prd = 0; | 20 | u8 tx_max_burst_prd = 0; |
21 | u8 tx_fifo_resize_max_num; | 21 | u8 tx_fifo_resize_max_num; |
22 | + u16 num_hc_interrupters; | 22 | + u16 num_hc_interrupters; |
23 | const char *usb_psy_name; | 23 | |
24 | int ret; | 24 | /* default to highest possible threshold */ |
25 | 25 | lpm_nyet_threshold = 0xf; | |
26 | @@ -XXX,XX +XXX,XX @@ static void dwc3_get_properties(struct dwc3 *dwc) | 26 | @@ -XXX,XX +XXX,XX @@ static void dwc3_get_properties(struct dwc3 *dwc) |
27 | */ | 27 | */ |
28 | tx_fifo_resize_max_num = 6; | 28 | tx_fifo_resize_max_num = 6; |
29 | 29 | ||
30 | + /* default to a single XHCI interrupter */ | 30 | + /* default to a single XHCI interrupter */ |
... | ... | ||
36 | @@ -XXX,XX +XXX,XX @@ static void dwc3_get_properties(struct dwc3 *dwc) | 36 | @@ -XXX,XX +XXX,XX @@ static void dwc3_get_properties(struct dwc3 *dwc) |
37 | &tx_thr_num_pkt_prd); | 37 | &tx_thr_num_pkt_prd); |
38 | device_property_read_u8(dev, "snps,tx-max-burst-prd", | 38 | device_property_read_u8(dev, "snps,tx-max-burst-prd", |
39 | &tx_max_burst_prd); | 39 | &tx_max_burst_prd); |
40 | + device_property_read_u16(dev, "num-hc-interrupters", | 40 | + device_property_read_u16(dev, "num-hc-interrupters", |
41 | + &num_hc_interrupters); | 41 | + &num_hc_interrupters); |
42 | + /* DWC3 core allowed to have a max of 8 interrupters */ | 42 | + /* DWC3 core allowed to have a max of 8 interrupters */ |
43 | + if (num_hc_interrupters > 8) | 43 | + if (num_hc_interrupters > 8) |
44 | + num_hc_interrupters = 8; | 44 | + num_hc_interrupters = 8; |
45 | + | 45 | + |
46 | dwc->do_fifo_resize = device_property_read_bool(dev, | 46 | dwc->do_fifo_resize = device_property_read_bool(dev, |
... | ... | ||
77 | 77 | ||
78 | diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c | 78 | diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c |
79 | index XXXXXXX..XXXXXXX 100644 | 79 | index XXXXXXX..XXXXXXX 100644 |
80 | --- a/drivers/usb/dwc3/host.c | 80 | --- a/drivers/usb/dwc3/host.c |
81 | +++ b/drivers/usb/dwc3/host.c | 81 | +++ b/drivers/usb/dwc3/host.c |
82 | @@ -XXX,XX +XXX,XX @@ static int dwc3_host_get_irq(struct dwc3 *dwc) | ||
83 | |||
84 | int dwc3_host_init(struct dwc3 *dwc) | ||
85 | { | ||
86 | - struct property_entry props[4]; | ||
87 | + struct property_entry props[5]; | ||
88 | struct platform_device *xhci; | ||
89 | int ret, irq; | ||
90 | int prop_idx = 0; | ||
91 | @@ -XXX,XX +XXX,XX @@ int dwc3_host_init(struct dwc3 *dwc) | 82 | @@ -XXX,XX +XXX,XX @@ int dwc3_host_init(struct dwc3 *dwc) |
92 | if (DWC3_VER_IS_WITHIN(DWC3, ANY, 300A)) | 83 | if (DWC3_VER_IS_WITHIN(DWC3, ANY, 300A)) |
93 | props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped"); | 84 | props[prop_idx++] = PROPERTY_ENTRY_BOOL("quirk-broken-port-ped"); |
94 | 85 | ||
95 | + props[prop_idx++] = PROPERTY_ENTRY_U16("num-hc-interrupters", | 86 | + props[prop_idx++] = PROPERTY_ENTRY_U16("num-hc-interrupters", |
96 | + dwc->num_hc_interrupters); | 87 | + dwc->num_hc_interrupters); |
97 | + | 88 | + |
98 | if (prop_idx) { | 89 | if (prop_idx) { |
99 | ret = device_create_managed_software_node(&xhci->dev, props, NULL); | 90 | ret = device_create_managed_software_node(&xhci->dev, props, NULL); |
100 | if (ret) { | 91 | if (ret) { | diff view generated by jsdifflib |
1 | Check for if the PCM format is supported during the hw_params callback. If | 1 | Add an USB jack type, in order to support notifying of a valid USB audio |
---|---|---|---|
2 | the profile is not supported then the userspace ALSA entity will receive an | 2 | device. Since USB audio devices can have a slew of different |
3 | error, and can take further action. | 3 | configurations that reach beyond the basic headset and headphone use cases, |
4 | classify these devices differently. | ||
4 | 5 | ||
6 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | ||
5 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
6 | --- | 8 | --- |
7 | sound/soc/qcom/qdsp6/q6usb.c | 5 ++++- | 9 | include/linux/mod_devicetable.h | 2 +- |
8 | 1 file changed, 4 insertions(+), 1 deletion(-) | 10 | include/sound/jack.h | 4 +++- |
11 | include/uapi/linux/input-event-codes.h | 3 ++- | ||
12 | sound/core/jack.c | 6 ++++-- | ||
13 | 4 files changed, 10 insertions(+), 5 deletions(-) | ||
9 | 14 | ||
10 | diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c | 15 | diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h |
11 | index XXXXXXX..XXXXXXX 100644 | 16 | index XXXXXXX..XXXXXXX 100644 |
12 | --- a/sound/soc/qcom/qdsp6/q6usb.c | 17 | --- a/include/linux/mod_devicetable.h |
13 | +++ b/sound/soc/qcom/qdsp6/q6usb.c | 18 | +++ b/include/linux/mod_devicetable.h |
14 | @@ -XXX,XX +XXX,XX @@ static int q6usb_hw_params(struct snd_pcm_substream *substream, | 19 | @@ -XXX,XX +XXX,XX @@ struct pcmcia_device_id { |
15 | struct snd_pcm_hw_params *params, | 20 | #define INPUT_DEVICE_ID_LED_MAX 0x0f |
16 | struct snd_soc_dai *dai) | 21 | #define INPUT_DEVICE_ID_SND_MAX 0x07 |
17 | { | 22 | #define INPUT_DEVICE_ID_FF_MAX 0x7f |
18 | - return 0; | 23 | -#define INPUT_DEVICE_ID_SW_MAX 0x10 |
19 | + struct q6usb_port_data *data = dev_get_drvdata(dai->dev); | 24 | +#define INPUT_DEVICE_ID_SW_MAX 0x11 |
20 | + int direction = substream->stream; | 25 | #define INPUT_DEVICE_ID_PROP_MAX 0x1f |
21 | + | 26 | |
22 | + return snd_soc_usb_find_format(data->active_idx, params, direction); | 27 | #define INPUT_DEVICE_ID_MATCH_BUS 1 |
23 | } | 28 | diff --git a/include/sound/jack.h b/include/sound/jack.h |
24 | 29 | index XXXXXXX..XXXXXXX 100644 | |
25 | static const struct snd_soc_dai_ops q6usb_ops = { | 30 | --- a/include/sound/jack.h |
31 | +++ b/include/sound/jack.h | ||
32 | @@ -XXX,XX +XXX,XX @@ struct input_dev; | ||
33 | * @SND_JACK_VIDEOOUT: Video out | ||
34 | * @SND_JACK_AVOUT: AV (Audio Video) out | ||
35 | * @SND_JACK_LINEIN: Line in | ||
36 | + * @SND_JACK_USB: USB audio device | ||
37 | * @SND_JACK_BTN_0: Button 0 | ||
38 | * @SND_JACK_BTN_1: Button 1 | ||
39 | * @SND_JACK_BTN_2: Button 2 | ||
40 | @@ -XXX,XX +XXX,XX @@ enum snd_jack_types { | ||
41 | SND_JACK_VIDEOOUT = 0x0010, | ||
42 | SND_JACK_AVOUT = SND_JACK_LINEOUT | SND_JACK_VIDEOOUT, | ||
43 | SND_JACK_LINEIN = 0x0020, | ||
44 | + SND_JACK_USB = 0x0040, | ||
45 | |||
46 | /* Kept separate from switches to facilitate implementation */ | ||
47 | SND_JACK_BTN_0 = 0x4000, | ||
48 | @@ -XXX,XX +XXX,XX @@ enum snd_jack_types { | ||
49 | }; | ||
50 | |||
51 | /* Keep in sync with definitions above */ | ||
52 | -#define SND_JACK_SWITCH_TYPES 6 | ||
53 | +#define SND_JACK_SWITCH_TYPES 7 | ||
54 | |||
55 | struct snd_jack { | ||
56 | struct list_head kctl_list; | ||
57 | diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h | ||
58 | index XXXXXXX..XXXXXXX 100644 | ||
59 | --- a/include/uapi/linux/input-event-codes.h | ||
60 | +++ b/include/uapi/linux/input-event-codes.h | ||
61 | @@ -XXX,XX +XXX,XX @@ | ||
62 | #define SW_MUTE_DEVICE 0x0e /* set = device disabled */ | ||
63 | #define SW_PEN_INSERTED 0x0f /* set = pen inserted */ | ||
64 | #define SW_MACHINE_COVER 0x10 /* set = cover closed */ | ||
65 | -#define SW_MAX 0x10 | ||
66 | +#define SW_USB_INSERT 0x11 /* set = USB audio device connected */ | ||
67 | +#define SW_MAX 0x11 | ||
68 | #define SW_CNT (SW_MAX+1) | ||
69 | |||
70 | /* | ||
71 | diff --git a/sound/core/jack.c b/sound/core/jack.c | ||
72 | index XXXXXXX..XXXXXXX 100644 | ||
73 | --- a/sound/core/jack.c | ||
74 | +++ b/sound/core/jack.c | ||
75 | @@ -XXX,XX +XXX,XX @@ static const int jack_switch_types[SND_JACK_SWITCH_TYPES] = { | ||
76 | SW_JACK_PHYSICAL_INSERT, | ||
77 | SW_VIDEOOUT_INSERT, | ||
78 | SW_LINEIN_INSERT, | ||
79 | + SW_USB_INSERT, | ||
80 | }; | ||
81 | #endif /* CONFIG_SND_JACK_INPUT_DEV */ | ||
82 | |||
83 | @@ -XXX,XX +XXX,XX @@ static ssize_t jack_kctl_id_read(struct file *file, | ||
84 | static const char * const jack_events_name[] = { | ||
85 | "HEADPHONE(0x0001)", "MICROPHONE(0x0002)", "LINEOUT(0x0004)", | ||
86 | "MECHANICAL(0x0008)", "VIDEOOUT(0x0010)", "LINEIN(0x0020)", | ||
87 | - "", "", "", "BTN_5(0x0200)", "BTN_4(0x0400)", "BTN_3(0x0800)", | ||
88 | - "BTN_2(0x1000)", "BTN_1(0x2000)", "BTN_0(0x4000)", "", | ||
89 | + "USB(0x0040)", "", "", "BTN_5(0x0200)", "BTN_4(0x0400)", | ||
90 | + "BTN_3(0x0800)", "BTN_2(0x1000)", "BTN_1(0x2000)", "BTN_0(0x4000)", | ||
91 | + "", | ||
92 | }; | ||
93 | |||
94 | /* the recommended buffer size is 256 */ | diff view generated by jsdifflib |
1 | Some vendor modules will utilize useful parsing and endpoint management | 1 | Some vendor modules will utilize useful parsing and endpoint management |
---|---|---|---|
2 | APIs to start audio playback/capture. | 2 | APIs to start audio playback/capture. |
3 | 3 | ||
4 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | ||
4 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 5 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
5 | --- | 6 | --- |
6 | sound/usb/card.c | 4 +++ | 7 | sound/usb/card.c | 4 +++ |
7 | sound/usb/endpoint.c | 1 + | 8 | sound/usb/endpoint.c | 1 + |
8 | sound/usb/helper.c | 1 + | 9 | sound/usb/helper.c | 1 + |
... | ... | ||
78 | return found; | 79 | return found; |
79 | } | 80 | } |
80 | 81 | ||
81 | +const struct audioformat * | 82 | +const struct audioformat * |
82 | +snd_usb_find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, | 83 | +snd_usb_find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, |
83 | + unsigned int rate, unsigned int channels, bool strict_match, | 84 | + unsigned int rate, unsigned int channels, bool strict_match, |
84 | + struct snd_usb_substream *subs) | 85 | + struct snd_usb_substream *subs) |
85 | +{ | 86 | +{ |
86 | + return find_format(fmt_list_head, format, rate, channels, strict_match, | 87 | + return find_format(fmt_list_head, format, rate, channels, strict_match, |
87 | + subs); | 88 | + subs); |
88 | +} | 89 | +} |
89 | +EXPORT_SYMBOL_GPL(snd_usb_find_format); | 90 | +EXPORT_SYMBOL_GPL(snd_usb_find_format); |
... | ... | ||
95 | true, subs); | 96 | true, subs); |
96 | } | 97 | } |
97 | 98 | ||
98 | +const struct audioformat * | 99 | +const struct audioformat * |
99 | +snd_usb_find_substream_format(struct snd_usb_substream *subs, | 100 | +snd_usb_find_substream_format(struct snd_usb_substream *subs, |
100 | + const struct snd_pcm_hw_params *params) | 101 | + const struct snd_pcm_hw_params *params) |
101 | +{ | 102 | +{ |
102 | + return find_substream_format(subs, params); | 103 | + return find_substream_format(subs, params); |
103 | +} | 104 | +} |
104 | +EXPORT_SYMBOL_GPL(snd_usb_find_substream_format); | 105 | +EXPORT_SYMBOL_GPL(snd_usb_find_substream_format); |
105 | + | 106 | + |
... | ... | ||
121 | - * that. | 122 | - * that. |
122 | - */ | 123 | - */ |
123 | -static int snd_usb_hw_params(struct snd_pcm_substream *substream, | 124 | -static int snd_usb_hw_params(struct snd_pcm_substream *substream, |
124 | - struct snd_pcm_hw_params *hw_params) | 125 | - struct snd_pcm_hw_params *hw_params) |
125 | +int snd_usb_hw_params(struct snd_usb_substream *subs, | 126 | +int snd_usb_hw_params(struct snd_usb_substream *subs, |
126 | + struct snd_pcm_hw_params *hw_params) | 127 | + struct snd_pcm_hw_params *hw_params) |
127 | { | 128 | { |
128 | - struct snd_usb_substream *subs = substream->runtime->private_data; | 129 | - struct snd_usb_substream *subs = substream->runtime->private_data; |
129 | struct snd_usb_audio *chip = subs->stream->chip; | 130 | struct snd_usb_audio *chip = subs->stream->chip; |
130 | const struct audioformat *fmt; | 131 | const struct audioformat *fmt; |
131 | const struct audioformat *sync_fmt; | 132 | const struct audioformat *sync_fmt; |
... | ... | ||
156 | + * if sg buffer is supported on the later version of alsa, we'll follow | 157 | + * if sg buffer is supported on the later version of alsa, we'll follow |
157 | + * that. | 158 | + * that. |
158 | */ | 159 | */ |
159 | -static int snd_usb_hw_free(struct snd_pcm_substream *substream) | 160 | -static int snd_usb_hw_free(struct snd_pcm_substream *substream) |
160 | +static int snd_usb_pcm_hw_params(struct snd_pcm_substream *substream, | 161 | +static int snd_usb_pcm_hw_params(struct snd_pcm_substream *substream, |
161 | + struct snd_pcm_hw_params *hw_params) | 162 | + struct snd_pcm_hw_params *hw_params) |
162 | { | 163 | { |
163 | struct snd_usb_substream *subs = substream->runtime->private_data; | 164 | struct snd_usb_substream *subs = substream->runtime->private_data; |
164 | + | 165 | + |
165 | + return snd_usb_hw_params(subs, hw_params); | 166 | + return snd_usb_hw_params(subs, hw_params); |
166 | +} | 167 | +} |
... | ... | ||
220 | int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip, | 221 | int snd_usb_audioformat_set_sync_ep(struct snd_usb_audio *chip, |
221 | struct audioformat *fmt); | 222 | struct audioformat *fmt); |
222 | 223 | ||
223 | +const struct audioformat * | 224 | +const struct audioformat * |
224 | +snd_usb_find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, | 225 | +snd_usb_find_format(struct list_head *fmt_list_head, snd_pcm_format_t format, |
225 | + unsigned int rate, unsigned int channels, bool strict_match, | 226 | + unsigned int rate, unsigned int channels, bool strict_match, |
226 | + struct snd_usb_substream *subs); | 227 | + struct snd_usb_substream *subs); |
227 | +const struct audioformat * | 228 | +const struct audioformat * |
228 | +snd_usb_find_substream_format(struct snd_usb_substream *subs, | 229 | +snd_usb_find_substream_format(struct snd_usb_substream *subs, |
229 | + const struct snd_pcm_hw_params *params); | 230 | + const struct snd_pcm_hw_params *params); |
230 | + | 231 | + |
231 | +int snd_usb_hw_params(struct snd_usb_substream *subs, | 232 | +int snd_usb_hw_params(struct snd_usb_substream *subs, |
232 | + struct snd_pcm_hw_params *hw_params); | 233 | + struct snd_pcm_hw_params *hw_params); |
233 | +int snd_usb_hw_free(struct snd_usb_substream *subs); | 234 | +int snd_usb_hw_free(struct snd_usb_substream *subs); |
234 | #endif /* __USBAUDIO_PCM_H */ | 235 | #endif /* __USBAUDIO_PCM_H */ | diff view generated by jsdifflib |
1 | Allow for checks on a specific USB audio device to see if a requested PCM | 1 | Allow for checks on a specific USB audio device to see if a requested PCM |
---|---|---|---|
2 | format is supported. This is needed for support for when playback is | 2 | format is supported. This is needed for support when playback is |
3 | initiated by the ASoC USB backend path. | 3 | initiated by the ASoC USB backend path. |
4 | 4 | ||
5 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | ||
5 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
6 | --- | 7 | --- |
7 | sound/usb/card.c | 40 ++++++++++++++++++++++++++++++++++++++++ | 8 | sound/usb/card.c | 32 ++++++++++++++++++++++++++++++++ |
8 | sound/usb/card.h | 11 +++++++++++ | 9 | sound/usb/card.h | 3 +++ |
9 | 2 files changed, 51 insertions(+) | 10 | 2 files changed, 35 insertions(+) |
10 | 11 | ||
11 | diff --git a/sound/usb/card.c b/sound/usb/card.c | 12 | diff --git a/sound/usb/card.c b/sound/usb/card.c |
12 | index XXXXXXX..XXXXXXX 100644 | 13 | index XXXXXXX..XXXXXXX 100644 |
13 | --- a/sound/usb/card.c | 14 | --- a/sound/usb/card.c |
14 | +++ b/sound/usb/card.c | 15 | +++ b/sound/usb/card.c |
15 | @@ -XXX,XX +XXX,XX @@ int snd_usb_unregister_platform_ops(void) | 16 | @@ -XXX,XX +XXX,XX @@ static DEFINE_MUTEX(register_mutex); |
16 | } | 17 | static struct snd_usb_audio *usb_chip[SNDRV_CARDS]; |
17 | EXPORT_SYMBOL_GPL(snd_usb_unregister_platform_ops); | 18 | static struct usb_driver usb_audio_driver; |
18 | 19 | ||
19 | +/* | 20 | +/* |
20 | + * Checks to see if requested audio profile, i.e sample rate, # of | 21 | + * Checks to see if requested audio profile, i.e sample rate, # of |
21 | + * channels, etc... is supported by the substream associated to the | 22 | + * channels, etc... is supported by the substream associated to the |
22 | + * USB audio device. | 23 | + * USB audio device. |
23 | + */ | 24 | + */ |
24 | +struct snd_usb_stream *snd_usb_find_suppported_substream(int card_idx, | 25 | +struct snd_usb_stream * |
25 | + struct snd_pcm_hw_params *params, int direction) | 26 | +snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params, |
27 | + int direction) | ||
26 | +{ | 28 | +{ |
27 | + struct snd_usb_audio *chip; | 29 | + struct snd_usb_audio *chip; |
28 | + struct snd_usb_substream *subs = NULL; | 30 | + struct snd_usb_substream *subs; |
29 | + struct snd_usb_stream *as; | 31 | + struct snd_usb_stream *as; |
30 | + const struct audioformat *fmt; | ||
31 | + | 32 | + |
32 | + /* | 33 | + /* |
33 | + * Register mutex is held when populating and clearing usb_chip | 34 | + * Register mutex is held when populating and clearing usb_chip |
34 | + * array. | 35 | + * array. |
35 | + */ | 36 | + */ |
36 | + mutex_lock(®ister_mutex); | 37 | + guard(mutex)(®ister_mutex); |
37 | + chip = usb_chip[card_idx]; | 38 | + chip = usb_chip[card_idx]; |
38 | + if (!chip) { | ||
39 | + mutex_unlock(®ister_mutex); | ||
40 | + return NULL; | ||
41 | + } | ||
42 | + | 39 | + |
43 | + if (enable[card_idx]) { | 40 | + if (chip && enable[card_idx]) { |
44 | + list_for_each_entry(as, &chip->pcm_list, list) { | 41 | + list_for_each_entry(as, &chip->pcm_list, list) { |
45 | + subs = &as->substream[direction]; | 42 | + subs = &as->substream[direction]; |
46 | + fmt = snd_usb_find_substream_format(subs, params); | 43 | + if (snd_usb_find_substream_format(subs, params)) |
47 | + if (fmt) { | ||
48 | + mutex_unlock(®ister_mutex); | ||
49 | + return as; | 44 | + return as; |
50 | + } | ||
51 | + } | 45 | + } |
52 | + } | 46 | + } |
53 | + mutex_unlock(®ister_mutex); | ||
54 | + | 47 | + |
55 | + return NULL; | 48 | + return NULL; |
56 | +} | 49 | +} |
57 | +EXPORT_SYMBOL_GPL(snd_usb_find_suppported_substream); | 50 | +EXPORT_SYMBOL_GPL(snd_usb_find_suppported_substream); |
58 | + | 51 | + |
... | ... | ||
61 | * called from usb_audio_disconnect() | 54 | * called from usb_audio_disconnect() |
62 | diff --git a/sound/usb/card.h b/sound/usb/card.h | 55 | diff --git a/sound/usb/card.h b/sound/usb/card.h |
63 | index XXXXXXX..XXXXXXX 100644 | 56 | index XXXXXXX..XXXXXXX 100644 |
64 | --- a/sound/usb/card.h | 57 | --- a/sound/usb/card.h |
65 | +++ b/sound/usb/card.h | 58 | +++ b/sound/usb/card.h |
66 | @@ -XXX,XX +XXX,XX @@ struct snd_usb_platform_ops { | 59 | @@ -XXX,XX +XXX,XX @@ struct snd_usb_stream { |
67 | 60 | struct list_head list; | |
68 | int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops); | 61 | }; |
69 | int snd_usb_unregister_platform_ops(void); | 62 | |
70 | + | 63 | +struct snd_usb_stream * |
71 | +#if IS_ENABLED(CONFIG_SND_USB_AUDIO) | 64 | +snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params, |
72 | +struct snd_usb_stream *snd_usb_find_suppported_substream(int card_idx, | 65 | + int direction); |
73 | + struct snd_pcm_hw_params *params, int direction); | ||
74 | +#else | ||
75 | +static struct snd_usb_stream *snd_usb_find_suppported_substream(int card_idx, | ||
76 | + struct snd_pcm_hw_params *params, int direction) | ||
77 | +{ | ||
78 | + return NULL; | ||
79 | +} | ||
80 | +#endif /* IS_ENABLED(CONFIG_SND_USB_AUDIO) */ | ||
81 | #endif /* __USBAUDIO_CARD_H */ | 66 | #endif /* __USBAUDIO_CARD_H */ | diff view generated by jsdifflib |
1 | The Q6USB backend can carry information about the available USB SND cards | 1 | Within the UAC descriptor, there is information describing the size of a |
---|---|---|---|
2 | and PCM devices discovered on the USB bus. The dev_token field is used by | 2 | sample (bSubframeSize/bSubslotSize) and the number of relevant bits |
3 | the audio DSP to notify the USB offload driver of which card and PCM index | 3 | (bBitResolution). Currently, fmt_bits carries only the bit resolution, |
4 | to enable playback on. Separate this into a dedicated API, so the USB | 4 | however, some offloading entities may also require the overall size of the |
5 | backend can set the dev_token accordingly. The audio DSP does not utilize | 5 | sample. Save this information in a separate parameter, as depending on the |
6 | this information until the AFE port start command is sent, which is done | 6 | UAC format type, the sample size can not easily be decoded from other |
7 | during the PCM prepare phase. | 7 | existing parameters. |
8 | 8 | ||
9 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | ||
9 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 10 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
10 | --- | 11 | --- |
11 | sound/soc/qcom/qdsp6/q6afe.c | 49 +++++++++++++++++++++++++----------- | 12 | sound/usb/card.h | 1 + |
12 | sound/soc/qcom/qdsp6/q6afe.h | 1 + | 13 | sound/usb/format.c | 1 + |
13 | 2 files changed, 36 insertions(+), 14 deletions(-) | 14 | 2 files changed, 2 insertions(+) |
14 | 15 | ||
15 | diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c | 16 | diff --git a/sound/usb/card.h b/sound/usb/card.h |
16 | index XXXXXXX..XXXXXXX 100644 | 17 | index XXXXXXX..XXXXXXX 100644 |
17 | --- a/sound/soc/qcom/qdsp6/q6afe.c | 18 | --- a/sound/usb/card.h |
18 | +++ b/sound/soc/qcom/qdsp6/q6afe.c | 19 | +++ b/sound/usb/card.h |
19 | @@ -XXX,XX +XXX,XX @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, | 20 | @@ -XXX,XX +XXX,XX @@ struct audioformat { |
20 | } | 21 | unsigned int channels; /* # channels */ |
21 | EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare); | 22 | unsigned int fmt_type; /* USB audio format type (1-3) */ |
22 | 23 | unsigned int fmt_bits; /* number of significant bits */ | |
23 | -static int afe_port_send_usb_dev_param(struct q6afe_port *port, struct q6afe_usb_cfg *cfg) | 24 | + unsigned int fmt_sz; /* overall audio sub frame/slot size */ |
24 | +/** | 25 | unsigned int frame_size; /* samples per frame for non-audio */ |
25 | + * afe_port_send_usb_dev_param() - Send USB dev token | 26 | unsigned char iface; /* interface number */ |
26 | + * | 27 | unsigned char altsetting; /* corresponding alternate setting */ |
27 | + * @port: Instance of afe port | 28 | diff --git a/sound/usb/format.c b/sound/usb/format.c |
28 | + * @cardidx: USB SND card index to reference | 29 | index XXXXXXX..XXXXXXX 100644 |
29 | + * @pcmidx: USB SND PCM device index to reference | 30 | --- a/sound/usb/format.c |
30 | + * | 31 | +++ b/sound/usb/format.c |
31 | + * The USB dev token carries information about which USB SND card instance and | 32 | @@ -XXX,XX +XXX,XX @@ static u64 parse_audio_format_i_type(struct snd_usb_audio *chip, |
32 | + * PCM device to execute the offload on. This information is carried through | ||
33 | + * to the stream enable QMI request, which is handled by the offload class | ||
34 | + * driver. The information is parsed to determine which USB device to query | ||
35 | + * the required resources for. | ||
36 | + */ | ||
37 | +int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx) | ||
38 | { | ||
39 | - union afe_port_config *pcfg = &port->port_cfg; | ||
40 | struct afe_param_id_usb_audio_dev_params usb_dev; | ||
41 | + int ret; | ||
42 | + | ||
43 | + memset(&usb_dev, 0, sizeof(usb_dev)); | ||
44 | + | ||
45 | + usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; | ||
46 | + usb_dev.dev_token = (cardidx << 16) | (pcmidx << 8); | ||
47 | + ret = q6afe_port_set_param_v2(port, &usb_dev, | ||
48 | + AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS, | ||
49 | + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(usb_dev)); | ||
50 | + if (ret) | ||
51 | + dev_err(port->afe->dev, "%s: AFE device param cmd failed %d\n", | ||
52 | + __func__, ret); | ||
53 | + | ||
54 | + return ret; | ||
55 | +} | ||
56 | +EXPORT_SYMBOL_GPL(afe_port_send_usb_dev_param); | ||
57 | + | ||
58 | +static int afe_port_send_usb_params(struct q6afe_port *port, struct q6afe_usb_cfg *cfg) | ||
59 | +{ | ||
60 | + union afe_port_config *pcfg = &port->port_cfg; | ||
61 | struct afe_param_id_usb_audio_dev_lpcm_fmt lpcm_fmt; | ||
62 | struct afe_param_id_usb_audio_svc_interval svc_int; | ||
63 | int ret = 0; | ||
64 | @@ -XXX,XX +XXX,XX @@ static int afe_port_send_usb_dev_param(struct q6afe_port *port, struct q6afe_usb | ||
65 | goto exit; | ||
66 | } | 33 | } |
67 | 34 | ||
68 | - memset(&usb_dev, 0, sizeof(usb_dev)); | 35 | fp->fmt_bits = sample_width; |
69 | memset(&lpcm_fmt, 0, sizeof(lpcm_fmt)); | 36 | + fp->fmt_sz = sample_bytes; |
70 | memset(&svc_int, 0, sizeof(svc_int)); | 37 | |
71 | 38 | if ((pcm_formats == 0) && | |
72 | - usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; | 39 | (format == 0 || format == BIT(UAC_FORMAT_TYPE_I_UNDEFINED))) { |
73 | - ret = q6afe_port_set_param_v2(port, &usb_dev, | ||
74 | - AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS, | ||
75 | - AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(usb_dev)); | ||
76 | - if (ret) { | ||
77 | - dev_err(port->afe->dev, "%s: AFE device param cmd failed %d\n", | ||
78 | - __func__, ret); | ||
79 | - goto exit; | ||
80 | - } | ||
81 | - | ||
82 | lpcm_fmt.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; | ||
83 | lpcm_fmt.endian = pcfg->usb_cfg.endian; | ||
84 | ret = q6afe_port_set_param_v2(port, &lpcm_fmt, | ||
85 | @@ -XXX,XX +XXX,XX @@ void q6afe_usb_port_prepare(struct q6afe_port *port, | ||
86 | pcfg->usb_cfg.num_channels = cfg->num_channels; | ||
87 | pcfg->usb_cfg.bit_width = cfg->bit_width; | ||
88 | |||
89 | - afe_port_send_usb_dev_param(port, cfg); | ||
90 | + afe_port_send_usb_params(port, cfg); | ||
91 | } | ||
92 | EXPORT_SYMBOL_GPL(q6afe_usb_port_prepare); | ||
93 | |||
94 | diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h | ||
95 | index XXXXXXX..XXXXXXX 100644 | ||
96 | --- a/sound/soc/qcom/qdsp6/q6afe.h | ||
97 | +++ b/sound/soc/qcom/qdsp6/q6afe.h | ||
98 | @@ -XXX,XX +XXX,XX @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, struct q6afe_tdm_cfg *cfg); | ||
99 | void q6afe_cdc_dma_port_prepare(struct q6afe_port *port, | ||
100 | struct q6afe_cdc_dma_cfg *cfg); | ||
101 | |||
102 | +int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx); | ||
103 | int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, | ||
104 | int clk_src, int clk_root, | ||
105 | unsigned int freq, int dir); | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
6 | 6 | ||
7 | If a PCM device is already in use, the check will return an error to | 7 | If a PCM device is already in use, the check will return an error to |
8 | userspace notifying that the stream is currently busy. This ensures that | 8 | userspace notifying that the stream is currently busy. This ensures that |
9 | only one path is using the USB substream. | 9 | only one path is using the USB substream. |
10 | 10 | ||
11 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | ||
11 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 12 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
12 | --- | 13 | --- |
13 | sound/usb/card.h | 1 + | 14 | sound/usb/card.h | 1 + |
14 | sound/usb/pcm.c | 19 +++++++++++++++++-- | 15 | sound/usb/pcm.c | 29 ++++++++++++++++++++++++++--- |
15 | sound/usb/qcom/qc_audio_offload.c | 15 ++++++++++++++- | 16 | 2 files changed, 27 insertions(+), 3 deletions(-) |
16 | 3 files changed, 32 insertions(+), 3 deletions(-) | ||
17 | 17 | ||
18 | diff --git a/sound/usb/card.h b/sound/usb/card.h | 18 | diff --git a/sound/usb/card.h b/sound/usb/card.h |
19 | index XXXXXXX..XXXXXXX 100644 | 19 | index XXXXXXX..XXXXXXX 100644 |
20 | --- a/sound/usb/card.h | 20 | --- a/sound/usb/card.h |
21 | +++ b/sound/usb/card.h | 21 | +++ b/sound/usb/card.h |
... | ... | ||
41 | + mutex_lock(&chip->mutex); | 41 | + mutex_lock(&chip->mutex); |
42 | + if (subs->opened) { | 42 | + if (subs->opened) { |
43 | + mutex_unlock(&chip->mutex); | 43 | + mutex_unlock(&chip->mutex); |
44 | + return -EBUSY; | 44 | + return -EBUSY; |
45 | + } | 45 | + } |
46 | + subs->opened = 1; | ||
47 | + mutex_unlock(&chip->mutex); | ||
46 | + | 48 | + |
47 | runtime->hw = snd_usb_hardware; | 49 | runtime->hw = snd_usb_hardware; |
48 | /* need an explicit sync to catch applptr update in low-latency mode */ | 50 | /* need an explicit sync to catch applptr update in low-latency mode */ |
49 | if (direction == SNDRV_PCM_STREAM_PLAYBACK && | 51 | if (direction == SNDRV_PCM_STREAM_PLAYBACK && |
50 | @@ -XXX,XX +XXX,XX @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream) | 52 | @@ -XXX,XX +XXX,XX @@ static int snd_usb_pcm_open(struct snd_pcm_substream *substream) |
51 | 53 | ||
52 | ret = setup_hw_info(runtime, subs); | 54 | ret = setup_hw_info(runtime, subs); |
53 | if (ret < 0) | 55 | if (ret < 0) |
54 | - return ret; | 56 | - return ret; |
55 | + goto out; | 57 | + goto err_open; |
56 | ret = snd_usb_autoresume(subs->stream->chip); | 58 | ret = snd_usb_autoresume(subs->stream->chip); |
57 | if (ret < 0) | 59 | if (ret < 0) |
58 | - return ret; | 60 | - return ret; |
59 | + goto out; | 61 | + goto err_open; |
60 | ret = snd_media_stream_init(subs, as->pcm, direction); | 62 | ret = snd_media_stream_init(subs, as->pcm, direction); |
61 | if (ret < 0) | 63 | if (ret < 0) |
62 | snd_usb_autosuspend(subs->stream->chip); | 64 | - snd_usb_autosuspend(subs->stream->chip); |
63 | + subs->opened = 1; | 65 | + goto err_resume; |
64 | +out: | 66 | + |
67 | + return 0; | ||
68 | + | ||
69 | +err_resume: | ||
70 | + snd_usb_autosuspend(subs->stream->chip); | ||
71 | +err_open: | ||
72 | + mutex_lock(&chip->mutex); | ||
73 | + subs->opened = 0; | ||
65 | + mutex_unlock(&chip->mutex); | 74 | + mutex_unlock(&chip->mutex); |
66 | + | 75 | + |
67 | return ret; | 76 | return ret; |
68 | } | 77 | } |
69 | 78 | ||
... | ... | ||
83 | + subs->opened = 0; | 92 | + subs->opened = 0; |
84 | + mutex_unlock(&chip->mutex); | 93 | + mutex_unlock(&chip->mutex); |
85 | 94 | ||
86 | return 0; | 95 | return 0; |
87 | } | 96 | } |
88 | diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c | ||
89 | index XXXXXXX..XXXXXXX 100644 | ||
90 | --- a/sound/usb/qcom/qc_audio_offload.c | ||
91 | +++ b/sound/usb/qcom/qc_audio_offload.c | ||
92 | @@ -XXX,XX +XXX,XX @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, | ||
93 | goto response; | ||
94 | } | ||
95 | |||
96 | + mutex_lock(&chip->mutex); | ||
97 | if (req_msg->enable) { | ||
98 | - if (info_idx < 0 || chip->system_suspend) { | ||
99 | + if (info_idx < 0 || chip->system_suspend || subs->opened) { | ||
100 | ret = -EBUSY; | ||
101 | + mutex_unlock(&chip->mutex); | ||
102 | + | ||
103 | goto response; | ||
104 | } | ||
105 | + subs->opened = 1; | ||
106 | } | ||
107 | + mutex_unlock(&chip->mutex); | ||
108 | |||
109 | if (req_msg->service_interval_valid) { | ||
110 | ret = get_data_interval_from_si(subs, | ||
111 | @@ -XXX,XX +XXX,XX @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, | ||
112 | if (!ret) | ||
113 | ret = prepare_qmi_response(subs, req_msg, &resp, | ||
114 | info_idx); | ||
115 | + if (ret < 0) { | ||
116 | + mutex_lock(&chip->mutex); | ||
117 | + subs->opened = 0; | ||
118 | + mutex_unlock(&chip->mutex); | ||
119 | + } | ||
120 | } else { | ||
121 | info = &uadev[pcm_card_num].info[info_idx]; | ||
122 | if (info->data_ep_pipe) { | ||
123 | @@ -XXX,XX +XXX,XX @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, | ||
124 | } | ||
125 | |||
126 | disable_audio_stream(subs); | ||
127 | + mutex_lock(&chip->mutex); | ||
128 | + subs->opened = 0; | ||
129 | + mutex_unlock(&chip->mutex); | ||
130 | } | ||
131 | |||
132 | response: | diff view generated by jsdifflib |
1 | Allow for different platforms to be notified on USB SND connect/disconnect | 1 | Allow for different platforms to be notified on USB SND connect/disconnect |
---|---|---|---|
2 | seqeunces. This allows for platform USB SND modules to properly initialize | 2 | sequences. This allows for platform USB SND modules to properly initialize |
3 | and populate internal structures with references to the USB SND chip | 3 | and populate internal structures with references to the USB SND chip |
4 | device. | 4 | device. |
5 | 5 | ||
6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
7 | --- | 7 | --- |
8 | sound/usb/card.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ | 8 | sound/usb/card.c | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ |
9 | sound/usb/card.h | 9 ++++++++ | 9 | sound/usb/card.h | 10 ++++++++++ |
10 | 2 files changed, 69 insertions(+) | 10 | 2 files changed, 59 insertions(+) |
11 | 11 | ||
12 | diff --git a/sound/usb/card.c b/sound/usb/card.c | 12 | diff --git a/sound/usb/card.c b/sound/usb/card.c |
13 | index XXXXXXX..XXXXXXX 100644 | 13 | index XXXXXXX..XXXXXXX 100644 |
14 | --- a/sound/usb/card.c | 14 | --- a/sound/usb/card.c |
15 | +++ b/sound/usb/card.c | 15 | +++ b/sound/usb/card.c |
... | ... | ||
28 | + * Only one set of platform operations can be registered to USB SND. The | 28 | + * Only one set of platform operations can be registered to USB SND. The |
29 | + * platform register operation is protected by the register_mutex. | 29 | + * platform register operation is protected by the register_mutex. |
30 | + */ | 30 | + */ |
31 | +int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops) | 31 | +int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops) |
32 | +{ | 32 | +{ |
33 | + int ret; | 33 | + guard(mutex)(®ister_mutex); |
34 | + | 34 | + if (platform_ops) |
35 | + mutex_lock(®ister_mutex); | 35 | + return -EEXIST; |
36 | + if (platform_ops) { | ||
37 | + ret = -EEXIST; | ||
38 | + goto out; | ||
39 | + } | ||
40 | + | 36 | + |
41 | + platform_ops = ops; | 37 | + platform_ops = ops; |
42 | +out: | ||
43 | + mutex_unlock(®ister_mutex); | ||
44 | + return 0; | 38 | + return 0; |
45 | +} | 39 | +} |
46 | +EXPORT_SYMBOL_GPL(snd_usb_register_platform_ops); | 40 | +EXPORT_SYMBOL_GPL(snd_usb_register_platform_ops); |
47 | + | 41 | + |
48 | +/* | 42 | +/* |
... | ... | ||
51 | + * | 45 | + * |
52 | + * The platform unregister operation is protected by the register_mutex. | 46 | + * The platform unregister operation is protected by the register_mutex. |
53 | + */ | 47 | + */ |
54 | +int snd_usb_unregister_platform_ops(void) | 48 | +int snd_usb_unregister_platform_ops(void) |
55 | +{ | 49 | +{ |
56 | + mutex_lock(®ister_mutex); | 50 | + guard(mutex)(®ister_mutex); |
57 | + platform_ops = NULL; | 51 | + platform_ops = NULL; |
58 | + mutex_unlock(®ister_mutex); | ||
59 | + | 52 | + |
60 | + return 0; | 53 | + return 0; |
61 | +} | 54 | +} |
62 | +EXPORT_SYMBOL_GPL(snd_usb_unregister_platform_ops); | 55 | +EXPORT_SYMBOL_GPL(snd_usb_unregister_platform_ops); |
63 | 56 | ||
64 | /* | 57 | /* |
65 | * disconnect streams | 58 | * Checks to see if requested audio profile, i.e sample rate, # of |
66 | @@ -XXX,XX +XXX,XX @@ static int usb_audio_probe(struct usb_interface *intf, | 59 | @@ -XXX,XX +XXX,XX @@ static int usb_audio_probe(struct usb_interface *intf, |
67 | chip->num_interfaces++; | 60 | chip->num_interfaces++; |
68 | usb_set_intfdata(intf, chip); | 61 | usb_set_intfdata(intf, chip); |
69 | atomic_dec(&chip->active); | 62 | atomic_dec(&chip->active); |
70 | + | 63 | + |
... | ... | ||
87 | struct snd_usb_endpoint *ep; | 80 | struct snd_usb_endpoint *ep; |
88 | @@ -XXX,XX +XXX,XX @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) | 81 | @@ -XXX,XX +XXX,XX @@ static int usb_audio_suspend(struct usb_interface *intf, pm_message_t message) |
89 | chip->system_suspend = chip->num_suspended_intf; | 82 | chip->system_suspend = chip->num_suspended_intf; |
90 | } | 83 | } |
91 | 84 | ||
92 | + mutex_lock(®ister_mutex); | ||
93 | + if (platform_ops && platform_ops->suspend_cb) | 85 | + if (platform_ops && platform_ops->suspend_cb) |
94 | + platform_ops->suspend_cb(intf, message); | 86 | + platform_ops->suspend_cb(intf, message); |
95 | + mutex_unlock(®ister_mutex); | ||
96 | + | 87 | + |
97 | return 0; | 88 | return 0; |
98 | } | 89 | } |
99 | 90 | ||
100 | @@ -XXX,XX +XXX,XX @@ static int usb_audio_resume(struct usb_interface *intf) | 91 | @@ -XXX,XX +XXX,XX @@ static int usb_audio_resume(struct usb_interface *intf) |
101 | 92 | ||
102 | snd_usb_midi_v2_resume_all(chip); | 93 | snd_usb_midi_v2_resume_all(chip); |
103 | 94 | ||
104 | + mutex_lock(®ister_mutex); | ||
105 | + if (platform_ops && platform_ops->resume_cb) | 95 | + if (platform_ops && platform_ops->resume_cb) |
106 | + platform_ops->resume_cb(intf); | 96 | + platform_ops->resume_cb(intf); |
107 | + mutex_unlock(®ister_mutex); | ||
108 | + | 97 | + |
109 | out: | 98 | out: |
110 | if (chip->num_suspended_intf == chip->system_suspend) { | 99 | if (chip->num_suspended_intf == chip->system_suspend) { |
111 | snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); | 100 | snd_power_change_state(chip->card, SNDRV_CTL_POWER_D0); |
112 | diff --git a/sound/usb/card.h b/sound/usb/card.h | 101 | diff --git a/sound/usb/card.h b/sound/usb/card.h |
... | ... | ||
122 | + void (*disconnect_cb)(struct snd_usb_audio *chip); | 111 | + void (*disconnect_cb)(struct snd_usb_audio *chip); |
123 | + void (*suspend_cb)(struct usb_interface *intf, pm_message_t message); | 112 | + void (*suspend_cb)(struct usb_interface *intf, pm_message_t message); |
124 | + void (*resume_cb)(struct usb_interface *intf); | 113 | + void (*resume_cb)(struct usb_interface *intf); |
125 | +}; | 114 | +}; |
126 | + | 115 | + |
116 | struct snd_usb_stream * | ||
117 | snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params, | ||
118 | int direction); | ||
119 | + | ||
127 | +int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops); | 120 | +int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops); |
128 | +int snd_usb_unregister_platform_ops(void); | 121 | +int snd_usb_unregister_platform_ops(void); |
129 | #endif /* __USBAUDIO_CARD_H */ | 122 | #endif /* __USBAUDIO_CARD_H */ | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
8 | to call the respective connection callback registered to the SND platform | 8 | to call the respective connection callback registered to the SND platform |
9 | driver. | 9 | driver. |
10 | 10 | ||
11 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 11 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
12 | --- | 12 | --- |
13 | sound/usb/card.c | 19 +++++++++++++++++++ | 13 | sound/usb/card.c | 21 +++++++++++++++++++++ |
14 | sound/usb/card.h | 2 ++ | 14 | sound/usb/card.h | 2 ++ |
15 | 2 files changed, 21 insertions(+) | 15 | 2 files changed, 23 insertions(+) |
16 | 16 | ||
17 | diff --git a/sound/usb/card.c b/sound/usb/card.c | 17 | diff --git a/sound/usb/card.c b/sound/usb/card.c |
18 | index XXXXXXX..XXXXXXX 100644 | 18 | index XXXXXXX..XXXXXXX 100644 |
19 | --- a/sound/usb/card.c | 19 | --- a/sound/usb/card.c |
20 | +++ b/sound/usb/card.c | 20 | +++ b/sound/usb/card.c |
21 | @@ -XXX,XX +XXX,XX @@ struct snd_usb_stream *snd_usb_find_suppported_substream(int card_idx, | 21 | @@ -XXX,XX +XXX,XX @@ int snd_usb_unregister_platform_ops(void) |
22 | } | 22 | } |
23 | EXPORT_SYMBOL_GPL(snd_usb_find_suppported_substream); | 23 | EXPORT_SYMBOL_GPL(snd_usb_unregister_platform_ops); |
24 | 24 | ||
25 | +/* | 25 | +/* |
26 | + * in case the platform driver was not ready at the time of USB SND | 26 | + * in case the platform driver was not ready at the time of USB SND |
27 | + * device connect, expose an API to discover all connected USB devices | 27 | + * device connect, expose an API to discover all connected USB devices |
28 | + * so it can populate any dependent resources/structures. | 28 | + * so it can populate any dependent resources/structures. |
29 | + */ | 29 | + */ |
30 | +void snd_usb_rediscover_devices(void) | 30 | +void snd_usb_rediscover_devices(void) |
31 | +{ | 31 | +{ |
32 | + int i; | 32 | + int i; |
33 | + | 33 | + |
34 | + mutex_lock(®ister_mutex); | 34 | + guard(mutex)(®ister_mutex); |
35 | + | ||
36 | + if (!platform_ops || !platform_ops->connect_cb) | ||
37 | + return; | ||
38 | + | ||
35 | + for (i = 0; i < SNDRV_CARDS; i++) { | 39 | + for (i = 0; i < SNDRV_CARDS; i++) { |
36 | + if (usb_chip[i]) | 40 | + if (usb_chip[i]) |
37 | + if (platform_ops && platform_ops->connect_cb) | 41 | + platform_ops->connect_cb(usb_chip[i]); |
38 | + platform_ops->connect_cb(usb_chip[i]); | ||
39 | + } | 42 | + } |
40 | + mutex_unlock(®ister_mutex); | ||
41 | +} | 43 | +} |
42 | +EXPORT_SYMBOL_GPL(snd_usb_rediscover_devices); | 44 | +EXPORT_SYMBOL_GPL(snd_usb_rediscover_devices); |
43 | + | 45 | + |
44 | /* | 46 | /* |
45 | * disconnect streams | 47 | * Checks to see if requested audio profile, i.e sample rate, # of |
46 | * called from usb_audio_disconnect() | 48 | * channels, etc... is supported by the substream associated to the |
47 | diff --git a/sound/usb/card.h b/sound/usb/card.h | 49 | diff --git a/sound/usb/card.h b/sound/usb/card.h |
48 | index XXXXXXX..XXXXXXX 100644 | 50 | index XXXXXXX..XXXXXXX 100644 |
49 | --- a/sound/usb/card.h | 51 | --- a/sound/usb/card.h |
50 | +++ b/sound/usb/card.h | 52 | +++ b/sound/usb/card.h |
51 | @@ -XXX,XX +XXX,XX @@ int snd_usb_unregister_platform_ops(void); | 53 | @@ -XXX,XX +XXX,XX @@ snd_usb_find_suppported_substream(int card_idx, struct snd_pcm_hw_params *params |
52 | #if IS_ENABLED(CONFIG_SND_USB_AUDIO) | 54 | |
53 | struct snd_usb_stream *snd_usb_find_suppported_substream(int card_idx, | 55 | int snd_usb_register_platform_ops(struct snd_usb_platform_ops *ops); |
54 | struct snd_pcm_hw_params *params, int direction); | 56 | int snd_usb_unregister_platform_ops(void); |
57 | + | ||
55 | +void snd_usb_rediscover_devices(void); | 58 | +void snd_usb_rediscover_devices(void); |
56 | #else | ||
57 | static struct snd_usb_stream *snd_usb_find_suppported_substream(int card_idx, | ||
58 | struct snd_pcm_hw_params *params, int direction) | ||
59 | { | ||
60 | return NULL; | ||
61 | } | ||
62 | +static void snd_usb_rediscover_devices(void) { } | ||
63 | #endif /* IS_ENABLED(CONFIG_SND_USB_AUDIO) */ | ||
64 | #endif /* __USBAUDIO_CARD_H */ | 59 | #endif /* __USBAUDIO_CARD_H */ | diff view generated by jsdifflib |
1 | Some platforms may have support for offloading USB audio devices to a | 1 | Some platforms may have support for offloading USB audio devices to a |
---|---|---|---|
2 | dedicated audio DSP. Introduce a set of APIs that allow for management of | 2 | dedicated audio DSP. Introduce a set of APIs that allow for management of |
3 | USB sound card and PCM devices enumerated by the USB SND class driver. | 3 | USB sound card and PCM devices enumerated by the USB SND class driver. |
4 | This allows for the ASoC components to be aware of what USB devices are | 4 | This allows for the ASoC components to be aware of what USB devices are |
5 | available for offloading. | 5 | available for offloading. |
6 | 6 | ||
7 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | ||
7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 8 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
8 | --- | 9 | --- |
9 | include/sound/soc-usb.h | 48 +++++++++++ | 10 | include/sound/soc-usb.h | 93 +++++++++++++++++ |
10 | sound/soc/Makefile | 2 +- | 11 | sound/soc/Kconfig | 10 ++ |
11 | sound/soc/soc-usb.c | 186 ++++++++++++++++++++++++++++++++++++++++ | 12 | sound/soc/Makefile | 2 + |
12 | 3 files changed, 235 insertions(+), 1 deletion(-) | 13 | sound/soc/soc-usb.c | 219 ++++++++++++++++++++++++++++++++++++++++ |
14 | 4 files changed, 324 insertions(+) | ||
13 | create mode 100644 include/sound/soc-usb.h | 15 | create mode 100644 include/sound/soc-usb.h |
14 | create mode 100644 sound/soc/soc-usb.c | 16 | create mode 100644 sound/soc/soc-usb.c |
15 | 17 | ||
16 | diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h | 18 | diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h |
17 | new file mode 100644 | 19 | new file mode 100644 |
18 | index XXXXXXX..XXXXXXX | 20 | index XXXXXXX..XXXXXXX |
19 | --- /dev/null | 21 | --- /dev/null |
20 | +++ b/include/sound/soc-usb.h | 22 | +++ b/include/sound/soc-usb.h |
21 | @@ -XXX,XX +XXX,XX @@ | 23 | @@ -XXX,XX +XXX,XX @@ |
22 | +/* SPDX-License-Identifier: GPL-2.0 | 24 | +/* SPDX-License-Identifier: GPL-2.0 |
23 | + * | 25 | + * |
24 | + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. | 26 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. |
25 | + */ | 27 | + */ |
26 | + | 28 | + |
27 | +#ifndef __LINUX_SND_SOC_USB_H | 29 | +#ifndef __LINUX_SND_SOC_USB_H |
28 | +#define __LINUX_SND_SOC_USB_H | 30 | +#define __LINUX_SND_SOC_USB_H |
29 | + | 31 | + |
30 | +/** | 32 | +#include <sound/soc.h> |
31 | + * struct snd_soc_usb_device | 33 | + |
32 | + * @card_idx - sound card index associated with USB device | 34 | +/** |
33 | + * @chip_idx - USB sound chip array index | 35 | + * struct snd_soc_usb_device - SoC USB representation of a USB sound device |
34 | + * @num_playback - number of playback streams | 36 | + * @card_idx: sound card index associated with USB device |
35 | + * @num_capture - number of capture streams | 37 | + * @chip_idx: USB sound chip array index |
38 | + * @cpcm_idx: capture PCM index array associated with USB device | ||
39 | + * @ppcm_idx: playback PCM index array associated with USB device | ||
40 | + * @num_capture: number of capture streams | ||
41 | + * @num_playback: number of playback streams | ||
42 | + * @list: list head for SoC USB devices | ||
36 | + **/ | 43 | + **/ |
37 | +struct snd_soc_usb_device { | 44 | +struct snd_soc_usb_device { |
38 | + int card_idx; | 45 | + int card_idx; |
39 | + int chip_idx; | 46 | + int chip_idx; |
47 | + | ||
48 | + /* PCM index arrays */ | ||
49 | + unsigned int *cpcm_idx; /* TODO: capture path is not tested yet */ | ||
50 | + unsigned int *ppcm_idx; | ||
51 | + int num_capture; /* TODO: capture path is not tested yet */ | ||
40 | + int num_playback; | 52 | + int num_playback; |
41 | + int num_capture; | 53 | + |
54 | + struct list_head list; | ||
42 | +}; | 55 | +}; |
43 | + | 56 | + |
44 | +/** | 57 | +/** |
45 | + * struct snd_soc_usb | 58 | + * struct snd_soc_usb - representation of a SoC USB backend entity |
46 | + * @list - list head for SND SOC struct list | 59 | + * @list: list head for SND SOC struct list |
47 | + * @dev - USB backend device reference | 60 | + * @component: reference to ASoC component |
48 | + * @component - reference to ASoC component | 61 | + * @connection_status_cb: callback to notify connection events |
49 | + * @connection_status_cb - callback to notify connection events | 62 | + * @priv_data: driver data |
50 | + * @priv_data - driver data | ||
51 | + **/ | 63 | + **/ |
52 | +struct snd_soc_usb { | 64 | +struct snd_soc_usb { |
53 | + struct list_head list; | 65 | + struct list_head list; |
54 | + struct device *dev; | ||
55 | + struct snd_soc_component *component; | 66 | + struct snd_soc_component *component; |
56 | + int (*connection_status_cb)(struct snd_soc_usb *usb, | 67 | + int (*connection_status_cb)(struct snd_soc_usb *usb, |
57 | + struct snd_soc_usb_device *sdev, bool connected); | 68 | + struct snd_soc_usb_device *sdev, |
69 | + bool connected); | ||
58 | + void *priv_data; | 70 | + void *priv_data; |
59 | +}; | 71 | +}; |
60 | + | 72 | + |
73 | +#if IS_ENABLED(CONFIG_SND_SOC_USB) | ||
61 | +int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); | 74 | +int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); |
62 | +int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); | 75 | +int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); |
63 | +void *snd_soc_usb_get_priv_data(struct device *usbdev); | 76 | +void *snd_soc_usb_find_priv_data(struct device *usbdev); |
64 | + | 77 | + |
65 | +struct snd_soc_usb *snd_soc_usb_add_port(struct device *dev, void *priv, | 78 | +struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, |
66 | + int (*connection_cb)(struct snd_soc_usb *usb, | 79 | + void *data); |
67 | + struct snd_soc_usb_device *sdev, bool connected)); | 80 | +void snd_soc_usb_free_port(struct snd_soc_usb *usb); |
68 | +int snd_soc_usb_remove_port(struct device *dev); | 81 | +void snd_soc_usb_add_port(struct snd_soc_usb *usb); |
69 | +#endif | 82 | +void snd_soc_usb_remove_port(struct snd_soc_usb *usb); |
83 | +#else | ||
84 | +static inline int snd_soc_usb_connect(struct device *usbdev, | ||
85 | + struct snd_soc_usb_device *sdev) | ||
86 | +{ | ||
87 | + return -ENODEV; | ||
88 | +} | ||
89 | + | ||
90 | +static inline int snd_soc_usb_disconnect(struct device *usbdev, | ||
91 | + struct snd_soc_usb_device *sdev) | ||
92 | +{ | ||
93 | + return -EINVAL; | ||
94 | +} | ||
95 | + | ||
96 | +static inline void *snd_soc_usb_find_priv_data(struct device *usbdev) | ||
97 | +{ | ||
98 | + return NULL; | ||
99 | +} | ||
100 | + | ||
101 | +static inline struct snd_soc_usb * | ||
102 | +snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data) | ||
103 | +{ | ||
104 | + return ERR_PTR(-ENOMEM); | ||
105 | +} | ||
106 | + | ||
107 | +static inline void snd_soc_usb_free_port(struct snd_soc_usb *usb) | ||
108 | +{ } | ||
109 | + | ||
110 | +static inline void snd_soc_usb_add_port(struct snd_soc_usb *usb) | ||
111 | +{ } | ||
112 | + | ||
113 | +static inline void snd_soc_usb_remove_port(struct snd_soc_usb *usb) | ||
114 | +{ } | ||
115 | +#endif /* IS_ENABLED(CONFIG_SND_SOC_USB) */ | ||
116 | +#endif /*__LINUX_SND_SOC_USB_H */ | ||
117 | diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig | ||
118 | index XXXXXXX..XXXXXXX 100644 | ||
119 | --- a/sound/soc/Kconfig | ||
120 | +++ b/sound/soc/Kconfig | ||
121 | @@ -XXX,XX +XXX,XX @@ config SND_SOC_UTILS_KUNIT_TEST | ||
122 | config SND_SOC_ACPI | ||
123 | tristate | ||
124 | |||
125 | +config SND_SOC_USB | ||
126 | + tristate "SoC based USB audio offloading" | ||
127 | + depends on SND_USB_AUDIO | ||
128 | + help | ||
129 | + Enable this option if an ASoC platform card has support to handle | ||
130 | + USB audio offloading. This enables the SoC USB layer, which will | ||
131 | + notify the ASoC USB DPCM backend DAI link about available USB audio | ||
132 | + devices. Based on the notifications, sequences to enable the audio | ||
133 | + stream can be taken based on the design. | ||
134 | + | ||
135 | # All the supported SoCs | ||
136 | source "sound/soc/adi/Kconfig" | ||
137 | source "sound/soc/amd/Kconfig" | ||
70 | diff --git a/sound/soc/Makefile b/sound/soc/Makefile | 138 | diff --git a/sound/soc/Makefile b/sound/soc/Makefile |
71 | index XXXXXXX..XXXXXXX 100644 | 139 | index XXXXXXX..XXXXXXX 100644 |
72 | --- a/sound/soc/Makefile | 140 | --- a/sound/soc/Makefile |
73 | +++ b/sound/soc/Makefile | 141 | +++ b/sound/soc/Makefile |
74 | @@ -XXX,XX +XXX,XX @@ | 142 | @@ -XXX,XX +XXX,XX @@ endif |
75 | # SPDX-License-Identifier: GPL-2.0 | 143 | |
76 | -snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-utils.o soc-dai.o soc-component.o | 144 | obj-$(CONFIG_SND_SOC_ACPI) += snd-soc-acpi.o |
77 | +snd-soc-core-objs := soc-core.o soc-dapm.o soc-jack.o soc-usb.o soc-utils.o soc-dai.o soc-component.o | 145 | |
78 | snd-soc-core-objs += soc-pcm.o soc-devres.o soc-ops.o soc-link.o soc-card.o | 146 | +obj-$(CONFIG_SND_SOC_USB) += soc-usb.o |
79 | snd-soc-core-$(CONFIG_SND_SOC_COMPRESS) += soc-compress.o | 147 | + |
80 | 148 | obj-$(CONFIG_SND_SOC) += snd-soc-core.o | |
149 | obj-$(CONFIG_SND_SOC) += codecs/ | ||
150 | obj-$(CONFIG_SND_SOC) += generic/ | ||
81 | diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c | 151 | diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c |
82 | new file mode 100644 | 152 | new file mode 100644 |
83 | index XXXXXXX..XXXXXXX | 153 | index XXXXXXX..XXXXXXX |
84 | --- /dev/null | 154 | --- /dev/null |
85 | +++ b/sound/soc/soc-usb.c | 155 | +++ b/sound/soc/soc-usb.c |
86 | @@ -XXX,XX +XXX,XX @@ | 156 | @@ -XXX,XX +XXX,XX @@ |
87 | +// SPDX-License-Identifier: GPL-2.0 | 157 | +// SPDX-License-Identifier: GPL-2.0 |
88 | +/* | 158 | +/* |
89 | + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. | 159 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. |
90 | + */ | 160 | + */ |
91 | +#include <linux/of.h> | 161 | +#include <linux/of.h> |
92 | +#include <linux/usb.h> | 162 | +#include <linux/usb.h> |
93 | +#include <sound/soc.h> | ||
94 | +#include <sound/soc-usb.h> | 163 | +#include <sound/soc-usb.h> |
95 | +#include "../usb/card.h" | 164 | +#include "../usb/card.h" |
96 | + | 165 | + |
97 | +static DEFINE_MUTEX(ctx_mutex); | 166 | +static DEFINE_MUTEX(ctx_mutex); |
98 | +static LIST_HEAD(usb_ctx_list); | 167 | +static LIST_HEAD(usb_ctx_list); |
... | ... | ||
106 | + return ERR_PTR(-ENODEV); | 175 | + return ERR_PTR(-ENODEV); |
107 | + | 176 | + |
108 | + return node; | 177 | + return node; |
109 | +} | 178 | +} |
110 | + | 179 | + |
111 | +static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device_node *node) | 180 | +static struct snd_soc_usb *snd_soc_usb_ctx_lookup(struct device_node *node) |
112 | +{ | 181 | +{ |
113 | + struct snd_soc_usb *ctx; | 182 | + struct snd_soc_usb *ctx; |
114 | + | 183 | + |
115 | + mutex_lock(&ctx_mutex); | 184 | + if (!node) |
185 | + return NULL; | ||
186 | + | ||
116 | + list_for_each_entry(ctx, &usb_ctx_list, list) { | 187 | + list_for_each_entry(ctx, &usb_ctx_list, list) { |
117 | + if (ctx->dev->of_node == node) { | 188 | + if (ctx->component->dev->of_node == node) |
118 | + mutex_unlock(&ctx_mutex); | ||
119 | + return ctx; | 189 | + return ctx; |
120 | + } | ||
121 | + } | 190 | + } |
122 | + mutex_unlock(&ctx_mutex); | ||
123 | + | 191 | + |
124 | + return NULL; | 192 | + return NULL; |
125 | +} | 193 | +} |
126 | + | 194 | + |
127 | +/** | 195 | +static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev) |
128 | + * snd_soc_usb_find_priv_data() - Retrieve private data stored | ||
129 | + * @dev: device reference | ||
130 | + * | ||
131 | + * Fetch the private data stored in the USB SND SOC structure. | ||
132 | + * | ||
133 | + */ | ||
134 | +void *snd_soc_usb_find_priv_data(struct device *dev) | ||
135 | +{ | 196 | +{ |
136 | + struct snd_soc_usb *ctx; | 197 | + struct snd_soc_usb *ctx; |
137 | + struct device_node *node; | 198 | + struct device_node *node; |
138 | + | 199 | + |
139 | + node = snd_soc_find_phandle(dev); | 200 | + node = snd_soc_find_phandle(dev); |
140 | + if (!IS_ERR(node)) { | 201 | + if (!IS_ERR(node)) { |
141 | + ctx = snd_soc_find_usb_ctx(node); | 202 | + ctx = snd_soc_usb_ctx_lookup(node); |
142 | + of_node_put(node); | 203 | + of_node_put(node); |
143 | + } else { | 204 | + } else { |
144 | + /* Check if backend device */ | 205 | + ctx = snd_soc_usb_ctx_lookup(dev->of_node); |
145 | + ctx = snd_soc_find_usb_ctx(dev->of_node); | ||
146 | + } | 206 | + } |
147 | + | 207 | + |
208 | + return ctx ? ctx : NULL; | ||
209 | +} | ||
210 | + | ||
211 | +/** | ||
212 | + * snd_soc_usb_find_priv_data() - Retrieve private data stored | ||
213 | + * @usbdev: device reference | ||
214 | + * | ||
215 | + * Fetch the private data stored in the USB SND SoC structure. | ||
216 | + * | ||
217 | + */ | ||
218 | +void *snd_soc_usb_find_priv_data(struct device *usbdev) | ||
219 | +{ | ||
220 | + struct snd_soc_usb *ctx; | ||
221 | + | ||
222 | + mutex_lock(&ctx_mutex); | ||
223 | + ctx = snd_soc_find_usb_ctx(usbdev); | ||
224 | + mutex_unlock(&ctx_mutex); | ||
225 | + | ||
148 | + return ctx ? ctx->priv_data : NULL; | 226 | + return ctx ? ctx->priv_data : NULL; |
149 | +} | 227 | +} |
150 | +EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data); | 228 | +EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data); |
151 | + | 229 | + |
152 | +/** | 230 | +/** |
153 | + * snd_soc_usb_add_port() - Add a USB backend port | 231 | + * snd_soc_usb_allocate_port() - allocate a SoC USB port for offloading support |
154 | + * @dev: USB backend device | 232 | + * @component: USB DPCM backend DAI component |
155 | + * @priv: private data | 233 | + * @data: private data |
156 | + * @connection_cb: connection status callback | 234 | + * |
157 | + * | 235 | + * Allocate and initialize a SoC USB port. The SoC USB port is used to communicate |
158 | + * Register a USB backend device to the SND USB SOC framework. Memory is | 236 | + * different USB audio devices attached, in order to start audio offloading handled |
159 | + * allocated as part of the USB backend device. | 237 | + * by an ASoC entity. USB device plug in/out events are signaled with a |
160 | + * | 238 | + * notification, but don't directly impact the memory allocated for the SoC USB |
161 | + */ | 239 | + * port. |
162 | +struct snd_soc_usb *snd_soc_usb_add_port(struct device *dev, void *priv, | 240 | + * |
163 | + int (*connection_cb)(struct snd_soc_usb *usb, | 241 | + */ |
164 | + struct snd_soc_usb_device *sdev, bool connected)) | 242 | +struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, |
243 | + void *data) | ||
165 | +{ | 244 | +{ |
166 | + struct snd_soc_usb *usb; | 245 | + struct snd_soc_usb *usb; |
167 | + | 246 | + |
168 | + usb = devm_kzalloc(dev, sizeof(*usb), GFP_KERNEL); | 247 | + usb = kzalloc(sizeof(*usb), GFP_KERNEL); |
169 | + if (!usb) | 248 | + if (!usb) |
170 | + return ERR_PTR(-ENOMEM); | 249 | + return ERR_PTR(-ENOMEM); |
171 | + | 250 | + |
172 | + usb->connection_status_cb = connection_cb; | 251 | + usb->component = component; |
173 | + usb->dev = dev; | 252 | + usb->priv_data = data; |
174 | + usb->priv_data = priv; | 253 | + |
175 | + | 254 | + return usb; |
255 | +} | ||
256 | +EXPORT_SYMBOL_GPL(snd_soc_usb_allocate_port); | ||
257 | + | ||
258 | +/** | ||
259 | + * snd_soc_usb_free_port() - free a SoC USB port used for offloading support | ||
260 | + * @usb: allocated SoC USB port | ||
261 | + * | ||
262 | + * Free and remove the SoC USB port from the available list of ports. This will | ||
263 | + * ensure that the communication between USB SND and ASoC is halted. | ||
264 | + * | ||
265 | + */ | ||
266 | +void snd_soc_usb_free_port(struct snd_soc_usb *usb) | ||
267 | +{ | ||
268 | + snd_soc_usb_remove_port(usb); | ||
269 | + kfree(usb); | ||
270 | +} | ||
271 | +EXPORT_SYMBOL_GPL(snd_soc_usb_free_port); | ||
272 | + | ||
273 | +/** | ||
274 | + * snd_soc_usb_add_port() - Add a USB backend port | ||
275 | + * @usb: soc usb port to add | ||
276 | + * | ||
277 | + * Register a USB backend DAI link to the USB SoC framework. Memory is allocated | ||
278 | + * as part of the USB backend DAI link. | ||
279 | + * | ||
280 | + */ | ||
281 | +void snd_soc_usb_add_port(struct snd_soc_usb *usb) | ||
282 | +{ | ||
176 | + mutex_lock(&ctx_mutex); | 283 | + mutex_lock(&ctx_mutex); |
177 | + list_add_tail(&usb->list, &usb_ctx_list); | 284 | + list_add_tail(&usb->list, &usb_ctx_list); |
178 | + mutex_unlock(&ctx_mutex); | 285 | + mutex_unlock(&ctx_mutex); |
179 | + | ||
180 | + return usb; | ||
181 | +} | 286 | +} |
182 | +EXPORT_SYMBOL_GPL(snd_soc_usb_add_port); | 287 | +EXPORT_SYMBOL_GPL(snd_soc_usb_add_port); |
183 | + | 288 | + |
184 | +/** | 289 | +/** |
185 | + * snd_soc_usb_remove_port() - Remove a USB backend port | 290 | + * snd_soc_usb_remove_port() - Remove a USB backend port |
186 | + * @dev: USB backend device | 291 | + * @usb: soc usb port to remove |
187 | + * | 292 | + * |
188 | + * Remove a USB backend device from USB SND SOC. Memory is freed when USB | 293 | + * Remove a USB backend DAI link from USB SoC. Memory is freed when USB backend |
189 | + * backend is removed. | 294 | + * DAI is removed, or when snd_soc_usb_free_port() is called. |
190 | + * | 295 | + * |
191 | + */ | 296 | + */ |
192 | +int snd_soc_usb_remove_port(struct device *dev) | 297 | +void snd_soc_usb_remove_port(struct snd_soc_usb *usb) |
193 | +{ | 298 | +{ |
194 | + struct snd_soc_usb *ctx, *tmp; | 299 | + struct snd_soc_usb *ctx, *tmp; |
195 | + | 300 | + |
196 | + mutex_lock(&ctx_mutex); | 301 | + mutex_lock(&ctx_mutex); |
197 | + list_for_each_entry_safe(ctx, tmp, &usb_ctx_list, list) { | 302 | + list_for_each_entry_safe(ctx, tmp, &usb_ctx_list, list) { |
198 | + if (ctx->dev == dev) { | 303 | + if (ctx == usb) { |
199 | + list_del(&ctx->list); | 304 | + list_del(&ctx->list); |
200 | + break; | 305 | + break; |
201 | + } | 306 | + } |
202 | + } | 307 | + } |
203 | + mutex_unlock(&ctx_mutex); | 308 | + mutex_unlock(&ctx_mutex); |
204 | + | ||
205 | + return 0; | ||
206 | +} | 309 | +} |
207 | +EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port); | 310 | +EXPORT_SYMBOL_GPL(snd_soc_usb_remove_port); |
208 | + | 311 | + |
209 | +/** | 312 | +/** |
210 | + * snd_soc_usb_connect() - Notification of USB device connection | 313 | + * snd_soc_usb_connect() - Notification of USB device connection |
211 | + * @usbdev: USB bus device | 314 | + * @usbdev: USB bus device |
212 | + * @card_idx: USB SND card instance | 315 | + * @sdev: USB SND device to add |
213 | + * | 316 | + * |
214 | + * Notify of a new USB SND device connection. The card_idx can be used to | 317 | + * Notify of a new USB SND device connection. The sdev->card_idx can be used to |
215 | + * handle how the DPCM backend selects, which device to enable USB offloading | 318 | + * handle how the DPCM backend selects, which device to enable USB offloading |
216 | + * on. | 319 | + * on. |
217 | + * | 320 | + * |
218 | + */ | 321 | + */ |
219 | +int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev) | 322 | +int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev) |
220 | +{ | 323 | +{ |
221 | + struct snd_soc_usb *ctx; | 324 | + struct snd_soc_usb *ctx; |
222 | + struct device_node *node; | ||
223 | + | 325 | + |
224 | + if (!usbdev) | 326 | + if (!usbdev) |
225 | + return -ENODEV; | 327 | + return -ENODEV; |
226 | + | 328 | + |
227 | + node = snd_soc_find_phandle(usbdev); | 329 | + mutex_lock(&ctx_mutex); |
228 | + if (IS_ERR(node)) | 330 | + ctx = snd_soc_find_usb_ctx(usbdev); |
229 | + return -ENODEV; | ||
230 | + | ||
231 | + ctx = snd_soc_find_usb_ctx(node); | ||
232 | + of_node_put(node); | ||
233 | + if (!ctx) | 331 | + if (!ctx) |
234 | + return -ENODEV; | 332 | + goto exit; |
235 | + | 333 | + |
236 | + if (ctx->connection_status_cb) | 334 | + if (ctx->connection_status_cb) |
237 | + ctx->connection_status_cb(ctx, sdev, true); | 335 | + ctx->connection_status_cb(ctx, sdev, true); |
238 | + | 336 | + |
337 | +exit: | ||
338 | + mutex_unlock(&ctx_mutex); | ||
339 | + | ||
239 | + return 0; | 340 | + return 0; |
240 | +} | 341 | +} |
241 | +EXPORT_SYMBOL_GPL(snd_soc_usb_connect); | 342 | +EXPORT_SYMBOL_GPL(snd_soc_usb_connect); |
242 | + | 343 | + |
243 | +/** | 344 | +/** |
244 | + * snd_soc_usb_disconnect() - Notification of USB device disconnection | 345 | + * snd_soc_usb_disconnect() - Notification of USB device disconnection |
245 | + * @usbdev: USB bus device | 346 | + * @usbdev: USB bus device |
347 | + * @sdev: USB SND device to remove | ||
246 | + * | 348 | + * |
247 | + * Notify of a new USB SND device disconnection to the USB backend. | 349 | + * Notify of a new USB SND device disconnection to the USB backend. |
248 | + * | 350 | + * |
249 | + */ | 351 | + */ |
250 | +int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev) | 352 | +int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev) |
251 | +{ | 353 | +{ |
252 | + struct snd_soc_usb *ctx; | 354 | + struct snd_soc_usb *ctx; |
253 | + struct device_node *node; | ||
254 | + | 355 | + |
255 | + if (!usbdev) | 356 | + if (!usbdev) |
256 | + return -ENODEV; | 357 | + return -ENODEV; |
257 | + | 358 | + |
258 | + node = snd_soc_find_phandle(usbdev); | 359 | + mutex_lock(&ctx_mutex); |
259 | + if (IS_ERR(node)) | 360 | + ctx = snd_soc_find_usb_ctx(usbdev); |
260 | + return -ENODEV; | ||
261 | + | ||
262 | + ctx = snd_soc_find_usb_ctx(node); | ||
263 | + of_node_put(node); | ||
264 | + if (!ctx) | 361 | + if (!ctx) |
265 | + return -ENODEV; | 362 | + goto exit; |
266 | + | 363 | + |
267 | + if (ctx->connection_status_cb) | 364 | + if (ctx->connection_status_cb) |
268 | + ctx->connection_status_cb(ctx, sdev, false); | 365 | + ctx->connection_status_cb(ctx, sdev, false); |
269 | + | 366 | + |
367 | +exit: | ||
368 | + mutex_unlock(&ctx_mutex); | ||
369 | + | ||
270 | + return 0; | 370 | + return 0; |
271 | +} | 371 | +} |
272 | +EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect); | 372 | +EXPORT_SYMBOL_GPL(snd_soc_usb_disconnect); |
373 | + | ||
374 | +MODULE_LICENSE("GPL"); | ||
375 | +MODULE_DESCRIPTION("SoC USB driver for offloading"); | diff view generated by jsdifflib |
1 | Introduce a check for if a particular PCM format is supported by the USB | 1 | Introduce a helper to check if a particular PCM format is supported by the |
---|---|---|---|
2 | audio device connected. If the USB audio device does not have an audio | 2 | USB audio device connected. If the USB audio device does not have an |
3 | profile which can support the requested format, then notify the USB | 3 | audio profile which can support the requested format, then notify the USB |
4 | backend. | 4 | backend. |
5 | 5 | ||
6 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | ||
6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
7 | --- | 8 | --- |
8 | include/sound/soc-usb.h | 3 +++ | 9 | include/sound/soc-usb.h | 11 +++++++++++ |
9 | sound/soc/soc-usb.c | 13 +++++++++++++ | 10 | sound/soc/soc-usb.c | 26 ++++++++++++++++++++++++++ |
10 | 2 files changed, 16 insertions(+) | 11 | 2 files changed, 37 insertions(+) |
11 | 12 | ||
12 | diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h | 13 | diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h |
13 | index XXXXXXX..XXXXXXX 100644 | 14 | index XXXXXXX..XXXXXXX 100644 |
14 | --- a/include/sound/soc-usb.h | 15 | --- a/include/sound/soc-usb.h |
15 | +++ b/include/sound/soc-usb.h | 16 | +++ b/include/sound/soc-usb.h |
16 | @@ -XXX,XX +XXX,XX @@ struct snd_soc_usb { | 17 | @@ -XXX,XX +XXX,XX @@ struct snd_soc_usb { |
17 | void *priv_data; | ||
18 | }; | 18 | }; |
19 | 19 | ||
20 | +int snd_soc_usb_find_format(int card_idx, struct snd_pcm_hw_params *params, | 20 | #if IS_ENABLED(CONFIG_SND_SOC_USB) |
21 | + int direction); | 21 | +int snd_soc_usb_find_supported_format(int card_idx, |
22 | + struct snd_pcm_hw_params *params, | ||
23 | + int direction); | ||
22 | + | 24 | + |
23 | int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); | 25 | int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); |
24 | int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); | 26 | int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); |
25 | void *snd_soc_usb_find_priv_data(struct device *usbdev); | 27 | void *snd_soc_usb_find_priv_data(struct device *usbdev); |
28 | @@ -XXX,XX +XXX,XX @@ void snd_soc_usb_free_port(struct snd_soc_usb *usb); | ||
29 | void snd_soc_usb_add_port(struct snd_soc_usb *usb); | ||
30 | void snd_soc_usb_remove_port(struct snd_soc_usb *usb); | ||
31 | #else | ||
32 | +static inline int | ||
33 | +snd_soc_usb_find_supported_format(int card_idx, struct snd_pcm_hw_params *params, | ||
34 | + int direction) | ||
35 | +{ | ||
36 | + return -EINVAL; | ||
37 | +} | ||
38 | + | ||
39 | static inline int snd_soc_usb_connect(struct device *usbdev, | ||
40 | struct snd_soc_usb_device *sdev) | ||
41 | { | ||
26 | diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c | 42 | diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c |
27 | index XXXXXXX..XXXXXXX 100644 | 43 | index XXXXXXX..XXXXXXX 100644 |
28 | --- a/sound/soc/soc-usb.c | 44 | --- a/sound/soc/soc-usb.c |
29 | +++ b/sound/soc/soc-usb.c | 45 | +++ b/sound/soc/soc-usb.c |
30 | @@ -XXX,XX +XXX,XX @@ void *snd_soc_usb_find_priv_data(struct device *dev) | 46 | @@ -XXX,XX +XXX,XX @@ void *snd_soc_usb_find_priv_data(struct device *usbdev) |
31 | } | 47 | } |
32 | EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data); | 48 | EXPORT_SYMBOL_GPL(snd_soc_usb_find_priv_data); |
33 | 49 | ||
34 | +int snd_soc_usb_find_format(int card_idx, struct snd_pcm_hw_params *params, | 50 | +/** |
35 | + int direction) | 51 | + * snd_soc_usb_find_supported_format() - Check if audio format is supported |
52 | + * @card_idx: USB sound chip array index | ||
53 | + * @params: PCM parameters | ||
54 | + * @direction: capture or playback | ||
55 | + * | ||
56 | + * Ensure that a requested audio profile from the ASoC side is able to be | ||
57 | + * supported by the USB device. | ||
58 | + * | ||
59 | + * Return 0 on success, negative on error. | ||
60 | + * | ||
61 | + */ | ||
62 | +int snd_soc_usb_find_supported_format(int card_idx, | ||
63 | + struct snd_pcm_hw_params *params, | ||
64 | + int direction) | ||
36 | +{ | 65 | +{ |
37 | + struct snd_usb_stream *as; | 66 | + struct snd_usb_stream *as; |
38 | + | 67 | + |
39 | + as = snd_usb_find_suppported_substream(card_idx, params, direction); | 68 | + as = snd_usb_find_suppported_substream(card_idx, params, direction); |
40 | + if (!as) | 69 | + if (!as) |
41 | + return -EOPNOTSUPP; | 70 | + return -EOPNOTSUPP; |
42 | + | 71 | + |
43 | + return 0; | 72 | + return 0; |
44 | +} | 73 | +} |
45 | +EXPORT_SYMBOL_GPL(snd_soc_usb_find_format); | 74 | +EXPORT_SYMBOL_GPL(snd_soc_usb_find_supported_format); |
46 | + | 75 | + |
47 | /** | 76 | /** |
48 | * snd_soc_usb_add_port() - Add a USB backend port | 77 | * snd_soc_usb_allocate_port() - allocate a SoC USB port for offloading support |
49 | * @dev: USB backend device | 78 | * @component: USB DPCM backend DAI component | diff view generated by jsdifflib |
1 | From: Mathias Nyman <mathias.nyman@linux.intel.com> | 1 | Expose API for creation of a jack control for notifying of available |
---|---|---|---|
2 | devices that are plugged in/discovered, and that support offloading. This | ||
3 | allows for control names to be standardized across implementations of USB | ||
4 | audio offloading. | ||
2 | 5 | ||
3 | Modify the XHCI drivers to accommodate for handling multiple event rings in | 6 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> |
4 | case there are multiple interrupters. Add the required APIs so clients are | ||
5 | able to allocate/request for an interrupter ring, and pass this information | ||
6 | back to the client driver. This allows for users to handle the resource | ||
7 | accordingly, such as passing the event ring base address to an audio DSP. | ||
8 | There is no actual support for multiple MSI/MSI-X vectors. | ||
9 | |||
10 | Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> | ||
11 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
12 | --- | 8 | --- |
13 | drivers/usb/host/xhci-debugfs.c | 2 +- | 9 | include/sound/soc-usb.h | 9 +++++++++ |
14 | drivers/usb/host/xhci-mem.c | 99 ++++++++++++++++++++++++++++++--- | 10 | sound/soc/soc-usb.c | 38 ++++++++++++++++++++++++++++++++++++++ |
15 | drivers/usb/host/xhci-ring.c | 2 +- | 11 | 2 files changed, 47 insertions(+) |
16 | drivers/usb/host/xhci.c | 51 +++++++++++------ | ||
17 | drivers/usb/host/xhci.h | 5 +- | ||
18 | 5 files changed, 130 insertions(+), 29 deletions(-) | ||
19 | 12 | ||
20 | diff --git a/drivers/usb/host/xhci-debugfs.c b/drivers/usb/host/xhci-debugfs.c | 13 | diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h |
21 | index XXXXXXX..XXXXXXX 100644 | 14 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/drivers/usb/host/xhci-debugfs.c | 15 | --- a/include/sound/soc-usb.h |
23 | +++ b/drivers/usb/host/xhci-debugfs.c | 16 | +++ b/include/sound/soc-usb.h |
24 | @@ -XXX,XX +XXX,XX @@ void xhci_debugfs_init(struct xhci_hcd *xhci) | 17 | @@ -XXX,XX +XXX,XX @@ int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); |
25 | "command-ring", | 18 | int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); |
26 | xhci->debugfs_root); | 19 | void *snd_soc_usb_find_priv_data(struct device *usbdev); |
27 | 20 | ||
28 | - xhci_debugfs_create_ring_dir(xhci, &xhci->interrupter->event_ring, | 21 | +int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, |
29 | + xhci_debugfs_create_ring_dir(xhci, &xhci->interrupters[0]->event_ring, | 22 | + struct snd_soc_jack *jack); |
30 | "event-ring", | 23 | + |
31 | xhci->debugfs_root); | 24 | struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, |
32 | 25 | void *data); | |
33 | diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c | 26 | void snd_soc_usb_free_port(struct snd_soc_usb *usb); |
27 | @@ -XXX,XX +XXX,XX @@ static inline void *snd_soc_usb_find_priv_data(struct device *usbdev) | ||
28 | return NULL; | ||
29 | } | ||
30 | |||
31 | +static inline int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, | ||
32 | + struct snd_soc_jack *jack) | ||
33 | +{ | ||
34 | + return 0; | ||
35 | +} | ||
36 | + | ||
37 | static inline struct snd_soc_usb * | ||
38 | snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data) | ||
39 | { | ||
40 | diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c | ||
34 | index XXXXXXX..XXXXXXX 100644 | 41 | index XXXXXXX..XXXXXXX 100644 |
35 | --- a/drivers/usb/host/xhci-mem.c | 42 | --- a/sound/soc/soc-usb.c |
36 | +++ b/drivers/usb/host/xhci-mem.c | 43 | +++ b/sound/soc/soc-usb.c |
37 | @@ -XXX,XX +XXX,XX @@ xhci_free_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir) | 44 | @@ -XXX,XX +XXX,XX @@ |
38 | kfree(ir); | 45 | */ |
46 | #include <linux/of.h> | ||
47 | #include <linux/usb.h> | ||
48 | + | ||
49 | +#include <sound/jack.h> | ||
50 | #include <sound/soc-usb.h> | ||
51 | + | ||
52 | #include "../usb/card.h" | ||
53 | |||
54 | static DEFINE_MUTEX(ctx_mutex); | ||
55 | @@ -XXX,XX +XXX,XX @@ static struct snd_soc_usb *snd_soc_find_usb_ctx(struct device *dev) | ||
56 | return ctx ? ctx : NULL; | ||
39 | } | 57 | } |
40 | 58 | ||
41 | +void xhci_remove_secondary_interrupter(struct usb_hcd *hcd, struct xhci_interrupter *ir) | 59 | +/* SOC USB sound kcontrols */ |
60 | +/** | ||
61 | + * snd_soc_usb_setup_offload_jack() - Create USB offloading jack | ||
62 | + * @component: USB DPCM backend DAI component | ||
63 | + * @jack: jack structure to create | ||
64 | + * | ||
65 | + * Creates a jack device for notifying userspace of the availability | ||
66 | + * of an offload capable device. | ||
67 | + * | ||
68 | + * Returns 0 on success, negative on error. | ||
69 | + * | ||
70 | + */ | ||
71 | +int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, | ||
72 | + struct snd_soc_jack *jack) | ||
42 | +{ | 73 | +{ |
43 | + struct xhci_hcd *xhci = hcd_to_xhci(hcd); | 74 | + int ret; |
44 | + unsigned int intr_num; | ||
45 | + | 75 | + |
46 | + /* interrupter 0 is primary interrupter, don't touch it */ | 76 | + ret = snd_soc_card_jack_new(component->card, "USB Offload Jack", |
47 | + if (!ir || !ir->intr_num || ir->intr_num >= xhci->max_interrupters) | 77 | + SND_JACK_USB, jack); |
48 | + xhci_dbg(xhci, "Invalid secondary interrupter, can't remove\n"); | 78 | + if (ret < 0) { |
49 | + | 79 | + dev_err(component->card->dev, "Unable to add USB offload jack: %d\n", |
50 | + /* fixme, should we check xhci->interrupter[intr_num] == ir */ | 80 | + ret); |
51 | + /* fixme locking */ | 81 | + return ret; |
52 | + | ||
53 | + spin_lock_irq(&xhci->lock); | ||
54 | + | ||
55 | + intr_num = ir->intr_num; | ||
56 | + | ||
57 | + xhci_remove_interrupter(xhci, ir); | ||
58 | + xhci->interrupters[intr_num] = NULL; | ||
59 | + | ||
60 | + spin_unlock_irq(&xhci->lock); | ||
61 | + | ||
62 | + xhci_free_interrupter(xhci, ir); | ||
63 | +} | ||
64 | +EXPORT_SYMBOL_GPL(xhci_remove_secondary_interrupter); | ||
65 | + | ||
66 | void xhci_mem_cleanup(struct xhci_hcd *xhci) | ||
67 | { | ||
68 | struct device *dev = xhci_to_hcd(xhci)->self.sysdev; | ||
69 | @@ -XXX,XX +XXX,XX @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) | ||
70 | |||
71 | cancel_delayed_work_sync(&xhci->cmd_timer); | ||
72 | |||
73 | - xhci_remove_interrupter(xhci, xhci->interrupter); | ||
74 | - xhci_free_interrupter(xhci, xhci->interrupter); | ||
75 | - xhci->interrupter = NULL; | ||
76 | - xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed primary event ring"); | ||
77 | + for (i = 0; i < xhci->max_interrupters; i++) { | ||
78 | + if (xhci->interrupters[i]) { | ||
79 | + xhci_remove_interrupter(xhci, xhci->interrupters[i]); | ||
80 | + xhci_free_interrupter(xhci, xhci->interrupters[i]); | ||
81 | + xhci->interrupters[i] = NULL; | ||
82 | + } | ||
83 | + } | ||
84 | + xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Freed interrupters"); | ||
85 | |||
86 | if (xhci->cmd_ring) | ||
87 | xhci_ring_free(xhci, xhci->cmd_ring); | ||
88 | @@ -XXX,XX +XXX,XX @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) | ||
89 | for (i = 0; i < xhci->num_port_caps; i++) | ||
90 | kfree(xhci->port_caps[i].psi); | ||
91 | kfree(xhci->port_caps); | ||
92 | + kfree(xhci->interrupters); | ||
93 | xhci->num_port_caps = 0; | ||
94 | |||
95 | xhci->usb2_rhub.ports = NULL; | ||
96 | @@ -XXX,XX +XXX,XX @@ void xhci_mem_cleanup(struct xhci_hcd *xhci) | ||
97 | xhci->rh_bw = NULL; | ||
98 | xhci->ext_caps = NULL; | ||
99 | xhci->port_caps = NULL; | ||
100 | + xhci->interrupters = NULL; | ||
101 | |||
102 | xhci->page_size = 0; | ||
103 | xhci->page_shift = 0; | ||
104 | @@ -XXX,XX +XXX,XX @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, | ||
105 | return -EINVAL; | ||
106 | } | ||
107 | |||
108 | + if (xhci->interrupters[intr_num]) { | ||
109 | + xhci_warn(xhci, "Interrupter %d\n already set up", intr_num); | ||
110 | + return -EINVAL; | ||
111 | + } | 82 | + } |
112 | + | 83 | + |
113 | + xhci->interrupters[intr_num] = ir; | 84 | + ret = snd_soc_component_set_jack(component, jack, NULL); |
114 | + ir->intr_num = intr_num; | 85 | + if (ret) { |
115 | ir->ir_set = &xhci->run_regs->ir_set[intr_num]; | 86 | + dev_err(component->card->dev, "Failed to set jack: %d\n", ret); |
116 | 87 | + return ret; | |
117 | /* set ERST count with the number of entries in the segment table */ | ||
118 | @@ -XXX,XX +XXX,XX @@ xhci_add_interrupter(struct xhci_hcd *xhci, struct xhci_interrupter *ir, | ||
119 | return 0; | ||
120 | } | ||
121 | |||
122 | +struct xhci_interrupter * | ||
123 | +xhci_create_secondary_interrupter(struct usb_hcd *hcd) | ||
124 | +{ | ||
125 | + struct xhci_hcd *xhci = hcd_to_xhci(hcd); | ||
126 | + struct xhci_interrupter *ir; | ||
127 | + unsigned int i; | ||
128 | + int err = -ENOSPC; | ||
129 | + | ||
130 | + if (!xhci->interrupters) | ||
131 | + return NULL; | ||
132 | + | ||
133 | + ir = xhci_alloc_interrupter(xhci, GFP_KERNEL); | ||
134 | + if (!ir) | ||
135 | + return NULL; | ||
136 | + | ||
137 | + spin_lock_irq(&xhci->lock); | ||
138 | + | ||
139 | + /* Find available secondary interrupter, interrupter 0 is reserved for primary */ | ||
140 | + for (i = 1; i < xhci->max_interrupters; i++) { | ||
141 | + if (xhci->interrupters[i] == NULL) { | ||
142 | + err = xhci_add_interrupter(xhci, ir, i); | ||
143 | + break; | ||
144 | + } | ||
145 | + } | 88 | + } |
146 | + | 89 | + |
147 | + spin_unlock_irq(&xhci->lock); | 90 | + return 0; |
91 | +} | ||
92 | +EXPORT_SYMBOL_GPL(snd_soc_usb_setup_offload_jack); | ||
148 | + | 93 | + |
149 | + if (err) { | 94 | /** |
150 | + xhci_warn(xhci, "Failed to add secondary interrupter, max interrupters %d\n", | 95 | * snd_soc_usb_find_priv_data() - Retrieve private data stored |
151 | + xhci->max_interrupters); | 96 | * @usbdev: device reference |
152 | + xhci_free_interrupter(xhci, ir); | ||
153 | + return NULL; | ||
154 | + } | ||
155 | + | ||
156 | + xhci_dbg(xhci, "Add secondary interrupter %d, max interrupters %d\n", | ||
157 | + i, xhci->max_interrupters); | ||
158 | + | ||
159 | + return ir; | ||
160 | +} | ||
161 | +EXPORT_SYMBOL_GPL(xhci_create_secondary_interrupter); | ||
162 | + | ||
163 | int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) | ||
164 | { | ||
165 | - dma_addr_t dma; | ||
166 | + struct xhci_interrupter *ir; | ||
167 | struct device *dev = xhci_to_hcd(xhci)->self.sysdev; | ||
168 | + dma_addr_t dma; | ||
169 | unsigned int val, val2; | ||
170 | u64 val_64; | ||
171 | u32 page_size, temp; | ||
172 | @@ -XXX,XX +XXX,XX @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) | ||
173 | /* Allocate and set up primary interrupter 0 with an event ring. */ | ||
174 | xhci_dbg_trace(xhci, trace_xhci_dbg_init, | ||
175 | "Allocating primary event ring"); | ||
176 | - xhci->interrupter = xhci_alloc_interrupter(xhci, flags); | ||
177 | - if (!xhci->interrupter) | ||
178 | + xhci->interrupters = kcalloc_node(xhci->max_interrupters, sizeof(*xhci->interrupters), | ||
179 | + flags, dev_to_node(dev)); | ||
180 | + | ||
181 | + ir = xhci_alloc_interrupter(xhci, flags); | ||
182 | + if (!ir) | ||
183 | goto fail; | ||
184 | |||
185 | - if (xhci_add_interrupter(xhci, xhci->interrupter, 0)) | ||
186 | + if (xhci_add_interrupter(xhci, ir, 0)) | ||
187 | goto fail; | ||
188 | |||
189 | xhci->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX; | ||
190 | diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c | ||
191 | index XXXXXXX..XXXXXXX 100644 | ||
192 | --- a/drivers/usb/host/xhci-ring.c | ||
193 | +++ b/drivers/usb/host/xhci-ring.c | ||
194 | @@ -XXX,XX +XXX,XX @@ irqreturn_t xhci_irq(struct usb_hcd *hcd) | ||
195 | writel(status, &xhci->op_regs->status); | ||
196 | |||
197 | /* This is the handler of the primary interrupter */ | ||
198 | - ir = xhci->interrupter; | ||
199 | + ir = xhci->interrupters[0]; | ||
200 | if (!hcd->msi_enabled) { | ||
201 | u32 irq_pending; | ||
202 | irq_pending = readl(&ir->ir_set->irq_pending); | ||
203 | diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c | ||
204 | index XXXXXXX..XXXXXXX 100644 | ||
205 | --- a/drivers/usb/host/xhci.c | ||
206 | +++ b/drivers/usb/host/xhci.c | ||
207 | @@ -XXX,XX +XXX,XX @@ static int xhci_init(struct usb_hcd *hcd) | ||
208 | |||
209 | static int xhci_run_finished(struct xhci_hcd *xhci) | ||
210 | { | ||
211 | - struct xhci_interrupter *ir = xhci->interrupter; | ||
212 | + struct xhci_interrupter *ir = xhci->interrupters[0]; | ||
213 | unsigned long flags; | ||
214 | u32 temp; | ||
215 | |||
216 | @@ -XXX,XX +XXX,XX @@ int xhci_run(struct usb_hcd *hcd) | ||
217 | u64 temp_64; | ||
218 | int ret; | ||
219 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | ||
220 | - struct xhci_interrupter *ir = xhci->interrupter; | ||
221 | + struct xhci_interrupter *ir = xhci->interrupters[0]; | ||
222 | /* Start the xHCI host controller running only after the USB 2.0 roothub | ||
223 | * is setup. | ||
224 | */ | ||
225 | @@ -XXX,XX +XXX,XX @@ void xhci_stop(struct usb_hcd *hcd) | ||
226 | { | ||
227 | u32 temp; | ||
228 | struct xhci_hcd *xhci = hcd_to_xhci(hcd); | ||
229 | - struct xhci_interrupter *ir = xhci->interrupter; | ||
230 | + struct xhci_interrupter *ir = xhci->interrupters[0]; | ||
231 | |||
232 | mutex_lock(&xhci->mutex); | ||
233 | |||
234 | @@ -XXX,XX +XXX,XX @@ EXPORT_SYMBOL_GPL(xhci_shutdown); | ||
235 | #ifdef CONFIG_PM | ||
236 | static void xhci_save_registers(struct xhci_hcd *xhci) | ||
237 | { | ||
238 | - struct xhci_interrupter *ir = xhci->interrupter; | ||
239 | + struct xhci_interrupter *ir; | ||
240 | + unsigned int i; | ||
241 | |||
242 | xhci->s3.command = readl(&xhci->op_regs->command); | ||
243 | xhci->s3.dev_nt = readl(&xhci->op_regs->dev_notification); | ||
244 | xhci->s3.dcbaa_ptr = xhci_read_64(xhci, &xhci->op_regs->dcbaa_ptr); | ||
245 | xhci->s3.config_reg = readl(&xhci->op_regs->config_reg); | ||
246 | |||
247 | - if (!ir) | ||
248 | - return; | ||
249 | + /* save both primary and all secondary interrupters */ | ||
250 | + /* fixme, shold we lock to prevent race with remove secondary interrupter? */ | ||
251 | + for (i = 0; i < xhci->max_interrupters; i++) { | ||
252 | + ir = xhci->interrupters[i]; | ||
253 | + if (!ir) | ||
254 | + continue; | ||
255 | |||
256 | - ir->s3_erst_size = readl(&ir->ir_set->erst_size); | ||
257 | - ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); | ||
258 | - ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); | ||
259 | - ir->s3_irq_pending = readl(&ir->ir_set->irq_pending); | ||
260 | - ir->s3_irq_control = readl(&ir->ir_set->irq_control); | ||
261 | + ir->s3_erst_size = readl(&ir->ir_set->erst_size); | ||
262 | + ir->s3_erst_base = xhci_read_64(xhci, &ir->ir_set->erst_base); | ||
263 | + ir->s3_erst_dequeue = xhci_read_64(xhci, &ir->ir_set->erst_dequeue); | ||
264 | + ir->s3_irq_pending = readl(&ir->ir_set->irq_pending); | ||
265 | + ir->s3_irq_control = readl(&ir->ir_set->irq_control); | ||
266 | + } | ||
267 | } | ||
268 | |||
269 | static void xhci_restore_registers(struct xhci_hcd *xhci) | ||
270 | { | ||
271 | - struct xhci_interrupter *ir = xhci->interrupter; | ||
272 | + struct xhci_interrupter *ir; | ||
273 | + unsigned int i; | ||
274 | |||
275 | writel(xhci->s3.command, &xhci->op_regs->command); | ||
276 | writel(xhci->s3.dev_nt, &xhci->op_regs->dev_notification); | ||
277 | xhci_write_64(xhci, xhci->s3.dcbaa_ptr, &xhci->op_regs->dcbaa_ptr); | ||
278 | writel(xhci->s3.config_reg, &xhci->op_regs->config_reg); | ||
279 | - writel(ir->s3_erst_size, &ir->ir_set->erst_size); | ||
280 | - xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base); | ||
281 | - xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue); | ||
282 | - writel(ir->s3_irq_pending, &ir->ir_set->irq_pending); | ||
283 | - writel(ir->s3_irq_control, &ir->ir_set->irq_control); | ||
284 | + | ||
285 | + /* FIXME should we lock to protect against freeing of interrupters */ | ||
286 | + for (i = 0; i < xhci->max_interrupters; i++) { | ||
287 | + ir = xhci->interrupters[i]; | ||
288 | + if (!ir) | ||
289 | + continue; | ||
290 | + | ||
291 | + writel(ir->s3_erst_size, &ir->ir_set->erst_size); | ||
292 | + xhci_write_64(xhci, ir->s3_erst_base, &ir->ir_set->erst_base); | ||
293 | + xhci_write_64(xhci, ir->s3_erst_dequeue, &ir->ir_set->erst_dequeue); | ||
294 | + writel(ir->s3_irq_pending, &ir->ir_set->irq_pending); | ||
295 | + writel(ir->s3_irq_control, &ir->ir_set->irq_control); | ||
296 | + } | ||
297 | } | ||
298 | |||
299 | static void xhci_set_cmd_ring_deq(struct xhci_hcd *xhci) | ||
300 | @@ -XXX,XX +XXX,XX @@ int xhci_resume(struct xhci_hcd *xhci, pm_message_t msg) | ||
301 | xhci_dbg(xhci, "// Disabling event ring interrupts\n"); | ||
302 | temp = readl(&xhci->op_regs->status); | ||
303 | writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status); | ||
304 | - xhci_disable_interrupter(xhci->interrupter); | ||
305 | + xhci_disable_interrupter(xhci->interrupters[0]); | ||
306 | |||
307 | xhci_dbg(xhci, "cleaning up memory\n"); | ||
308 | xhci_mem_cleanup(xhci); | ||
309 | diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h | ||
310 | index XXXXXXX..XXXXXXX 100644 | ||
311 | --- a/drivers/usb/host/xhci.h | ||
312 | +++ b/drivers/usb/host/xhci.h | ||
313 | @@ -XXX,XX +XXX,XX @@ struct xhci_hcd { | ||
314 | struct reset_control *reset; | ||
315 | /* data structures */ | ||
316 | struct xhci_device_context_array *dcbaa; | ||
317 | - struct xhci_interrupter *interrupter; | ||
318 | + struct xhci_interrupter **interrupters; | ||
319 | struct xhci_ring *cmd_ring; | ||
320 | unsigned int cmd_ring_state; | ||
321 | #define CMD_RING_STATE_RUNNING (1 << 0) | ||
322 | @@ -XXX,XX +XXX,XX @@ struct xhci_container_ctx *xhci_alloc_container_ctx(struct xhci_hcd *xhci, | ||
323 | int type, gfp_t flags); | ||
324 | void xhci_free_container_ctx(struct xhci_hcd *xhci, | ||
325 | struct xhci_container_ctx *ctx); | ||
326 | +struct xhci_interrupter *xhci_create_secondary_interrupter(struct usb_hcd *hcd); | ||
327 | +void xhci_remove_secondary_interrupter(struct usb_hcd | ||
328 | + *hcd, struct xhci_interrupter *ir); | ||
329 | |||
330 | /* xHCI host controller glue */ | ||
331 | typedef void (*xhci_get_quirks_t)(struct device *, struct xhci_hcd *); | diff view generated by jsdifflib |
1 | From: Mathias Nyman <mathias.nyman@linux.intel.com> | 1 | USB SND needs to know how the USB offload path is being routed. This would |
---|---|---|---|
2 | allow for applications to open the corresponding sound card and pcm device | ||
3 | when it wants to take the audio offload path. This callback should return | ||
4 | the mapped indexes based on the USB SND device information. | ||
2 | 5 | ||
3 | Expose xhci_stop_endpoint_sync() which is a synchronous variant of | 6 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> |
4 | xhci_queue_stop_endpoint(). This is useful for client drivers that are | ||
5 | using the secondary interrupters, and need to stop/clean up the current | ||
6 | session. The stop endpoint command handler will also take care of cleaning | ||
7 | up the ring. | ||
8 | |||
9 | Modifications to repurpose the new API into existing stop endpoint | ||
10 | sequences was implemented by Wesley Cheng. | ||
11 | |||
12 | Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> | ||
13 | Co-developed-by: Wesley Cheng <quic_wcheng@quicinc.com> | ||
14 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
15 | --- | 8 | --- |
16 | drivers/usb/host/xhci.c | 61 ++++++++++++++++++++++++++++++----------- | 9 | include/sound/soc-usb.h | 25 +++++++++++++++++++++++++ |
17 | drivers/usb/host/xhci.h | 2 ++ | 10 | sound/soc/soc-usb.c | 37 +++++++++++++++++++++++++++++++++++++ |
18 | 2 files changed, 47 insertions(+), 16 deletions(-) | 11 | 2 files changed, 62 insertions(+) |
19 | 12 | ||
20 | diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c | 13 | diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h |
21 | index XXXXXXX..XXXXXXX 100644 | 14 | index XXXXXXX..XXXXXXX 100644 |
22 | --- a/drivers/usb/host/xhci.c | 15 | --- a/include/sound/soc-usb.h |
23 | +++ b/drivers/usb/host/xhci.c | 16 | +++ b/include/sound/soc-usb.h |
24 | @@ -XXX,XX +XXX,XX @@ static int xhci_reserve_bandwidth(struct xhci_hcd *xhci, | 17 | @@ -XXX,XX +XXX,XX @@ |
25 | return -ENOMEM; | 18 | |
19 | #include <sound/soc.h> | ||
20 | |||
21 | +enum snd_soc_usb_kctl { | ||
22 | + SND_SOC_USB_KCTL_CARD_ROUTE, | ||
23 | + SND_SOC_USB_KCTL_PCM_ROUTE, | ||
24 | +}; | ||
25 | + | ||
26 | /** | ||
27 | * struct snd_soc_usb_device - SoC USB representation of a USB sound device | ||
28 | * @card_idx: sound card index associated with USB device | ||
29 | @@ -XXX,XX +XXX,XX @@ struct snd_soc_usb_device { | ||
30 | * @list: list head for SND SOC struct list | ||
31 | * @component: reference to ASoC component | ||
32 | * @connection_status_cb: callback to notify connection events | ||
33 | + * @update_offload_route_info: callback to fetch mapped ASoC card and pcm | ||
34 | + * device pair. This is unrelated to the concept | ||
35 | + * of DAPM route. The "route" argument carries | ||
36 | + * an array used for a kcontrol output for either | ||
37 | + * the card or pcm index. "path" determines the | ||
38 | + * which entry to look for. (ie mapped card or pcm) | ||
39 | * @priv_data: driver data | ||
40 | **/ | ||
41 | struct snd_soc_usb { | ||
42 | @@ -XXX,XX +XXX,XX @@ struct snd_soc_usb { | ||
43 | int (*connection_status_cb)(struct snd_soc_usb *usb, | ||
44 | struct snd_soc_usb_device *sdev, | ||
45 | bool connected); | ||
46 | + int (*update_offload_route_info)(struct snd_soc_component *component, | ||
47 | + int card, int pcm, int direction, | ||
48 | + enum snd_soc_usb_kctl path, | ||
49 | + long *route); | ||
50 | void *priv_data; | ||
51 | }; | ||
52 | |||
53 | @@ -XXX,XX +XXX,XX @@ void *snd_soc_usb_find_priv_data(struct device *usbdev); | ||
54 | |||
55 | int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, | ||
56 | struct snd_soc_jack *jack); | ||
57 | +int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, | ||
58 | + int direction, enum snd_soc_usb_kctl path, | ||
59 | + long *route); | ||
60 | |||
61 | struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, | ||
62 | void *data); | ||
63 | @@ -XXX,XX +XXX,XX @@ static inline int snd_soc_usb_setup_offload_jack(struct snd_soc_component *compo | ||
64 | return 0; | ||
26 | } | 65 | } |
27 | 66 | ||
28 | +/* | 67 | +static int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, |
29 | + * Synchronous XHCI stop endpoint helper. Issues the stop endpoint command and | 68 | + int direction, enum snd_soc_usb_kctl path, |
30 | + * waits for the command completion before returning. | 69 | + long *route) |
70 | +{ | ||
71 | + return -ENODEV; | ||
72 | +} | ||
73 | + | ||
74 | static inline struct snd_soc_usb * | ||
75 | snd_soc_usb_allocate_port(struct snd_soc_component *component, void *data) | ||
76 | { | ||
77 | diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c | ||
78 | index XXXXXXX..XXXXXXX 100644 | ||
79 | --- a/sound/soc/soc-usb.c | ||
80 | +++ b/sound/soc/soc-usb.c | ||
81 | @@ -XXX,XX +XXX,XX @@ int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, | ||
82 | } | ||
83 | EXPORT_SYMBOL_GPL(snd_soc_usb_setup_offload_jack); | ||
84 | |||
85 | +/** | ||
86 | + * snd_soc_usb_update_offload_route - Find active USB offload path | ||
87 | + * @dev: USB device to get offload status | ||
88 | + * @card: USB card index | ||
89 | + * @pcm: USB PCM device index | ||
90 | + * @direction: playback or capture direction | ||
91 | + * @path: pcm or card index | ||
92 | + * @route: pointer to route output array | ||
93 | + * | ||
94 | + * Fetch the current status for the USB SND card and PCM device indexes | ||
95 | + * specified. The "route" argument should be an array of integers being | ||
96 | + * used for a kcontrol output. The first element should have the selected | ||
97 | + * card index, and the second element should have the selected pcm device | ||
98 | + * index. | ||
31 | + */ | 99 | + */ |
32 | +int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, int suspend, | 100 | +int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, |
33 | + gfp_t gfp_flags) | 101 | + int direction, enum snd_soc_usb_kctl path, |
102 | + long *route) | ||
34 | +{ | 103 | +{ |
35 | + struct xhci_command *command; | 104 | + struct snd_soc_usb *ctx; |
36 | + unsigned long flags; | 105 | + int ret = -ENODEV; |
37 | + int ret; | ||
38 | + | 106 | + |
39 | + command = xhci_alloc_command(xhci, true, GFP_KERNEL); | 107 | + mutex_lock(&ctx_mutex); |
40 | + if (!command) | 108 | + ctx = snd_soc_find_usb_ctx(dev); |
41 | + return -ENOMEM; | 109 | + if (!ctx) |
110 | + goto exit; | ||
42 | + | 111 | + |
43 | + spin_lock_irqsave(&xhci->lock, flags); | 112 | + if (ctx->update_offload_route_info) |
44 | + ret = xhci_queue_stop_endpoint(xhci, command, ep->vdev->slot_id, | 113 | + ret = ctx->update_offload_route_info(ctx->component, card, pcm, |
45 | + ep->ep_index, suspend); | 114 | + direction, path, route); |
46 | + if (ret < 0) { | 115 | +exit: |
47 | + spin_unlock_irqrestore(&xhci->lock, flags); | 116 | + mutex_unlock(&ctx_mutex); |
48 | + goto out; | ||
49 | + } | ||
50 | + | ||
51 | + xhci_ring_cmd_db(xhci); | ||
52 | + spin_unlock_irqrestore(&xhci->lock, flags); | ||
53 | + | ||
54 | + ret = wait_for_completion_timeout(command->completion, msecs_to_jiffies(3000)); | ||
55 | + if (!ret) | ||
56 | + xhci_warn(xhci, "%s: Unable to stop endpoint.\n", | ||
57 | + __func__); | ||
58 | + | ||
59 | + if (command->status == COMP_COMMAND_ABORTED || | ||
60 | + command->status == COMP_COMMAND_RING_STOPPED) { | ||
61 | + xhci_warn(xhci, "Timeout while waiting for stop endpoint command\n"); | ||
62 | + ret = -ETIME; | ||
63 | + } | ||
64 | +out: | ||
65 | + xhci_free_command(xhci, command); | ||
66 | + | 117 | + |
67 | + return ret; | 118 | + return ret; |
68 | +} | 119 | +} |
69 | 120 | +EXPORT_SYMBOL_GPL(snd_soc_usb_update_offload_route); | |
70 | /* Issue a configure endpoint command or evaluate context command | ||
71 | * and wait for it to finish. | ||
72 | @@ -XXX,XX +XXX,XX @@ static void xhci_endpoint_reset(struct usb_hcd *hcd, | ||
73 | struct xhci_virt_device *vdev; | ||
74 | struct xhci_virt_ep *ep; | ||
75 | struct xhci_input_control_ctx *ctrl_ctx; | ||
76 | - struct xhci_command *stop_cmd, *cfg_cmd; | ||
77 | + struct xhci_command *cfg_cmd; | ||
78 | unsigned int ep_index; | ||
79 | unsigned long flags; | ||
80 | u32 ep_flag; | ||
81 | @@ -XXX,XX +XXX,XX @@ static void xhci_endpoint_reset(struct usb_hcd *hcd, | ||
82 | if (ep_flag == SLOT_FLAG || ep_flag == EP0_FLAG) | ||
83 | return; | ||
84 | |||
85 | - stop_cmd = xhci_alloc_command(xhci, true, GFP_NOWAIT); | ||
86 | - if (!stop_cmd) | ||
87 | - return; | ||
88 | - | ||
89 | cfg_cmd = xhci_alloc_command_with_ctx(xhci, true, GFP_NOWAIT); | ||
90 | if (!cfg_cmd) | ||
91 | goto cleanup; | ||
92 | @@ -XXX,XX +XXX,XX @@ static void xhci_endpoint_reset(struct usb_hcd *hcd, | ||
93 | goto cleanup; | ||
94 | } | ||
95 | |||
96 | - err = xhci_queue_stop_endpoint(xhci, stop_cmd, udev->slot_id, | ||
97 | - ep_index, 0); | ||
98 | + spin_unlock_irqrestore(&xhci->lock, flags); | ||
99 | + | 121 | + |
100 | + err = xhci_stop_endpoint_sync(xhci, ep, 0, GFP_NOWAIT); | 122 | /** |
101 | if (err < 0) { | 123 | * snd_soc_usb_find_priv_data() - Retrieve private data stored |
102 | - spin_unlock_irqrestore(&xhci->lock, flags); | 124 | * @usbdev: device reference |
103 | - xhci_free_command(xhci, cfg_cmd); | ||
104 | xhci_dbg(xhci, "%s: Failed to queue stop ep command, %d ", | ||
105 | __func__, err); | ||
106 | goto cleanup; | ||
107 | } | ||
108 | |||
109 | - xhci_ring_cmd_db(xhci); | ||
110 | - spin_unlock_irqrestore(&xhci->lock, flags); | ||
111 | - | ||
112 | - wait_for_completion(stop_cmd->completion); | ||
113 | - | ||
114 | spin_lock_irqsave(&xhci->lock, flags); | ||
115 | - | ||
116 | /* config ep command clears toggle if add and drop ep flags are set */ | ||
117 | ctrl_ctx = xhci_get_input_control_ctx(cfg_cmd->in_ctx); | ||
118 | if (!ctrl_ctx) { | ||
119 | @@ -XXX,XX +XXX,XX @@ static void xhci_endpoint_reset(struct usb_hcd *hcd, | ||
120 | |||
121 | xhci_free_command(xhci, cfg_cmd); | ||
122 | cleanup: | ||
123 | - xhci_free_command(xhci, stop_cmd); | ||
124 | spin_lock_irqsave(&xhci->lock, flags); | ||
125 | if (ep->ep_state & EP_SOFT_CLEAR_TOGGLE) | ||
126 | ep->ep_state &= ~EP_SOFT_CLEAR_TOGGLE; | ||
127 | diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h | ||
128 | index XXXXXXX..XXXXXXX 100644 | ||
129 | --- a/drivers/usb/host/xhci.h | ||
130 | +++ b/drivers/usb/host/xhci.h | ||
131 | @@ -XXX,XX +XXX,XX @@ void xhci_ring_doorbell_for_active_rings(struct xhci_hcd *xhci, | ||
132 | void xhci_cleanup_command_queue(struct xhci_hcd *xhci); | ||
133 | void inc_deq(struct xhci_hcd *xhci, struct xhci_ring *ring); | ||
134 | unsigned int count_trbs(u64 addr, u64 len); | ||
135 | +int xhci_stop_endpoint_sync(struct xhci_hcd *xhci, struct xhci_virt_ep *ep, | ||
136 | + int suspend, gfp_t gfp_flags); | ||
137 | |||
138 | /* xHCI roothub code */ | ||
139 | void xhci_set_link_state(struct xhci_hcd *xhci, struct xhci_port *port, | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
36 | 36 | ||
37 | diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c | 37 | diff --git a/sound/soc/soc-usb.c b/sound/soc/soc-usb.c |
38 | index XXXXXXX..XXXXXXX 100644 | 38 | index XXXXXXX..XXXXXXX 100644 |
39 | --- a/sound/soc/soc-usb.c | 39 | --- a/sound/soc/soc-usb.c |
40 | +++ b/sound/soc/soc-usb.c | 40 | +++ b/sound/soc/soc-usb.c |
41 | @@ -XXX,XX +XXX,XX @@ struct snd_soc_usb *snd_soc_usb_add_port(struct device *dev, void *priv, | 41 | @@ -XXX,XX +XXX,XX @@ void snd_soc_usb_add_port(struct snd_soc_usb *usb) |
42 | mutex_lock(&ctx_mutex); | ||
42 | list_add_tail(&usb->list, &usb_ctx_list); | 43 | list_add_tail(&usb->list, &usb_ctx_list); |
43 | mutex_unlock(&ctx_mutex); | 44 | mutex_unlock(&ctx_mutex); |
44 | 45 | + | |
45 | + snd_usb_rediscover_devices(); | 46 | + snd_usb_rediscover_devices(); |
46 | + | ||
47 | return usb; | ||
48 | } | 47 | } |
49 | EXPORT_SYMBOL_GPL(snd_soc_usb_add_port); | 48 | EXPORT_SYMBOL_GPL(snd_soc_usb_add_port); | diff view generated by jsdifflib |
1 | Add a dt-binding to describe the definition of enabling the Q6 USB backend | 1 | With the introduction of the soc-usb driver, add documentation highlighting |
---|---|---|---|
2 | device for audio offloading. The node carries information, which is passed | 2 | details on how to utilize the new driver and how it interacts with |
3 | along to the QC USB SND class driver counterpart. These parameters will be | 3 | different components in USB SND and ASoC. It provides examples on how to |
4 | utilized during QMI stream enable requests. | 4 | implement the drivers that will need to be introduced in order to enable |
5 | USB audio offloading. | ||
5 | 6 | ||
6 | Reviewed-by: Rob Herring <robh@kernel.org> | ||
7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
8 | --- | 8 | --- |
9 | .../devicetree/bindings/sound/qcom,q6usb.yaml | 55 +++++++++++++++++++ | 9 | Documentation/sound/soc/index.rst | 1 + |
10 | 1 file changed, 55 insertions(+) | 10 | Documentation/sound/soc/usb.rst | 482 ++++++++++++++++++++++++++++++ |
11 | create mode 100644 Documentation/devicetree/bindings/sound/qcom,q6usb.yaml | 11 | 2 files changed, 483 insertions(+) |
12 | create mode 100644 Documentation/sound/soc/usb.rst | ||
12 | 13 | ||
13 | diff --git a/Documentation/devicetree/bindings/sound/qcom,q6usb.yaml b/Documentation/devicetree/bindings/sound/qcom,q6usb.yaml | 14 | diff --git a/Documentation/sound/soc/index.rst b/Documentation/sound/soc/index.rst |
15 | index XXXXXXX..XXXXXXX 100644 | ||
16 | --- a/Documentation/sound/soc/index.rst | ||
17 | +++ b/Documentation/sound/soc/index.rst | ||
18 | @@ -XXX,XX +XXX,XX @@ The documentation is spilt into the following sections:- | ||
19 | jack | ||
20 | dpcm | ||
21 | codec-to-codec | ||
22 | + usb | ||
23 | diff --git a/Documentation/sound/soc/usb.rst b/Documentation/sound/soc/usb.rst | ||
14 | new file mode 100644 | 24 | new file mode 100644 |
15 | index XXXXXXX..XXXXXXX | 25 | index XXXXXXX..XXXXXXX |
16 | --- /dev/null | 26 | --- /dev/null |
17 | +++ b/Documentation/devicetree/bindings/sound/qcom,q6usb.yaml | 27 | +++ b/Documentation/sound/soc/usb.rst |
18 | @@ -XXX,XX +XXX,XX @@ | 28 | @@ -XXX,XX +XXX,XX @@ |
19 | +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) | 29 | +================ |
20 | +%YAML 1.2 | 30 | +ASoC USB support |
21 | +--- | 31 | +================ |
22 | +$id: http://devicetree.org/schemas/sound/qcom,q6usb.yaml# | 32 | + |
23 | +$schema: http://devicetree.org/meta-schemas/core.yaml# | 33 | +Overview |
24 | + | 34 | +======== |
25 | +title: Qualcomm ASoC DPCM USB backend DAI | 35 | +In order to leverage the existing USB sound device support in ALSA, the |
26 | + | 36 | +ASoC USB APIs are introduced to allow the subsystems to exchange |
27 | +maintainers: | 37 | +configuration information. |
28 | + - Wesley Cheng <quic_wcheng@quicinc.com> | 38 | + |
29 | + | 39 | +One potential use case would be to support USB audio offloading, which is |
30 | +description: | 40 | +an implementation that allows for an alternate power-optimized path in the audio |
31 | + The USB port is a supported AFE path on the Q6 DSP. This ASoC DPCM | 41 | +subsystem to handle the transfer of audio data over the USB bus. This would |
32 | + backend DAI will communicate the required settings to initialize the | 42 | +let the main processor to stay in lower power modes for longer duration. The |
33 | + XHCI host controller properly for enabling the offloaded audio stream. | 43 | +following is an example design of how the ASoC and ALSA pieces can be connected |
34 | + Parameters defined under this node will carry settings, which will be | 44 | +together to achieve this: |
35 | + passed along during the QMI stream enable request and configuration of | 45 | + |
36 | + the XHCI host controller. | 46 | +:: |
37 | + | 47 | + |
38 | +allOf: | 48 | + USB | ASoC |
39 | + - $ref: dai-common.yaml# | 49 | + | _________________________ |
40 | + | 50 | + | | ASoC Platform card | |
41 | +properties: | 51 | + | |_________________________| |
42 | + compatible: | 52 | + | | | |
43 | + enum: | 53 | + | ___V____ ____V____ |
44 | + - qcom,q6usb | 54 | + | |ASoC BE | |ASoC FE | |
45 | + | 55 | + | |DAI LNK | |DAI LNK | |
46 | + iommus: | 56 | + | |________| |_________| |
47 | + maxItems: 1 | 57 | + | ^ ^ ^ |
48 | + | 58 | + | | |________| |
49 | + "#sound-dai-cells": | 59 | + | ___V____ | |
50 | + const: 1 | 60 | + | |SoC-USB | | |
51 | + | 61 | + ________ ________ | | | |
52 | + qcom,usb-audio-intr-idx: | 62 | + |USB SND |<--->|USBSND |<------------>|________| | |
53 | + description: | 63 | + |(card.c)| |offld |<---------- | |
54 | + Desired XHCI interrupter number to use. Depending on the audio DSP | 64 | + |________| |________|___ | | | |
55 | + on the platform, it will operate on a specific XHCI interrupter. | 65 | + ^ ^ | | | ____________V_________ |
56 | + $ref: /schemas/types.yaml#/definitions/uint16 | 66 | + | | | | | |IPC | |
57 | + maximum: 8 | 67 | + __ V_______________V_____ | | | |______________________| |
58 | + | 68 | + |USB SND (endpoint.c) | | | | ^ |
59 | +required: | 69 | + |_________________________| | | | | |
60 | + - compatible | 70 | + ^ | | | ___________V___________ |
61 | + - "#sound-dai-cells" | 71 | + | | | |->|audio DSP | |
62 | + - qcom,usb-audio-intr-idx | 72 | + ___________V_____________ | | |_______________________| |
63 | + | 73 | + |XHCI HCD |<- | |
64 | +additionalProperties: false | 74 | + |_________________________| | |
65 | + | 75 | + |
66 | +examples: | 76 | + |
67 | + - | | 77 | +SoC USB driver |
68 | + dais { | 78 | +============== |
69 | + compatible = "qcom,q6usb"; | 79 | +Structures |
70 | + #sound-dai-cells = <1>; | 80 | +---------- |
71 | + iommus = <&apps_smmu 0x180f 0x0>; | 81 | +``struct snd_soc_usb`` |
72 | + qcom,usb-audio-intr-idx = /bits/ 16 <2>; | 82 | + |
73 | + }; | 83 | + - ``list``: list head for SND SoC struct list |
84 | + - ``component``: reference to ASoC component | ||
85 | + - ``connection_status_cb``: callback to notify connection events | ||
86 | + - ``update_offload_route_info``: callback to fetch selected USB sound card/PCM | ||
87 | + device | ||
88 | + - ``priv_data``: driver data | ||
89 | + | ||
90 | +The snd_soc_usb structure can be referenced using the ASoC platform card | ||
91 | +device, or a USB device (udev->dev). This is created by the ASoC BE DAI | ||
92 | +link, and the USB sound entity will be able to pass information to the | ||
93 | +ASoC BE DAI link using this structure. | ||
94 | + | ||
95 | +``struct snd_soc_usb_device`` | ||
96 | + | ||
97 | + - ``card_idx``: sound card index associated with USB sound device | ||
98 | + - ``chip_idx``: USB sound chip array index | ||
99 | + - ``cpcm_idx``: capture pcm device indexes associated with the USB sound device | ||
100 | + - ``ppcm_idx``: playback pcm device indexes associated with the USB sound device | ||
101 | + - ``num_playback``: number of playback streams | ||
102 | + - ``num_capture``: number of capture streams | ||
103 | + - ``list``: list head for the USB sound device list | ||
104 | + | ||
105 | +The struct snd_soc_usb_device is created by the USB sound offload driver. | ||
106 | +This will carry basic parameters/limitations that will be used to | ||
107 | +determine the possible offloading paths for this USB audio device. | ||
108 | + | ||
109 | +Functions | ||
110 | +--------- | ||
111 | +.. code-block:: rst | ||
112 | + | ||
113 | + int snd_soc_usb_find_supported_format(int card_idx, | ||
114 | + struct snd_pcm_hw_params *params, int direction) | ||
115 | +.. | ||
116 | + | ||
117 | + - ``card_idx``: the index into the USB sound chip array. | ||
118 | + - ``params``: Requested PCM parameters from the USB DPCM BE DAI link | ||
119 | + - ``direction``: capture or playback | ||
120 | + | ||
121 | +**snd_soc_usb_find_supported_format()** ensures that the requested audio profile | ||
122 | +being requested by the external DSP is supported by the USB device. | ||
123 | + | ||
124 | +Returns 0 on success, and -EOPNOTSUPP on failure. | ||
125 | + | ||
126 | +.. code-block:: rst | ||
127 | + | ||
128 | + int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev) | ||
129 | +.. | ||
130 | + | ||
131 | + - ``usbdev``: the usb device that was discovered | ||
132 | + - ``sdev``: capabilities of the device | ||
133 | + | ||
134 | +**snd_soc_usb_connect()** notifies the ASoC USB DCPM BE DAI link of a USB | ||
135 | +audio device detection. This can be utilized in the BE DAI | ||
136 | +driver to keep track of available USB audio devices. This is intended | ||
137 | +to be called by the USB offload driver residing in USB SND. | ||
138 | + | ||
139 | +Returns 0 on success, negative error code on failure. | ||
140 | + | ||
141 | +.. code-block:: rst | ||
142 | + | ||
143 | + int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev) | ||
144 | +.. | ||
145 | + | ||
146 | + - ``usbdev``: the usb device that was removed | ||
147 | + - ``sdev``: capabilities to free | ||
148 | + | ||
149 | +**snd_soc_usb_disconnect()** notifies the ASoC USB DCPM BE DAI link of a USB | ||
150 | +audio device removal. This is intended to be called by the USB offload | ||
151 | +driver that resides in USB SND. | ||
152 | + | ||
153 | +.. code-block:: rst | ||
154 | + | ||
155 | + void *snd_soc_usb_find_priv_data(struct device *usbdev) | ||
156 | +.. | ||
157 | + | ||
158 | + - ``usbdev``: the usb device to reference to find private data | ||
159 | + | ||
160 | +**snd_soc_usb_find_priv_data()** fetches the private data saved to the SoC USB | ||
161 | +device. | ||
162 | + | ||
163 | +Returns pointer to priv_data on success, NULL on failure. | ||
164 | + | ||
165 | +.. code-block:: rst | ||
166 | + | ||
167 | + int snd_soc_usb_setup_offload_jack(struct snd_soc_component *component, | ||
168 | + struct snd_soc_jack *jack) | ||
169 | +.. | ||
170 | + | ||
171 | + - ``component``: ASoC component to add the jack | ||
172 | + - ``jack``: jack component to populate | ||
173 | + | ||
174 | +**snd_soc_usb_setup_offload_jack()** is a helper to add a sound jack control to | ||
175 | +the platform sound card. This will allow for consistent naming to be used on | ||
176 | +designs that support USB audio offloading. Additionally, this will enable the | ||
177 | +jack to notify of changes. | ||
178 | + | ||
179 | +Returns 0 on success, negative otherwise. | ||
180 | + | ||
181 | +.. code-block:: rst | ||
182 | + | ||
183 | + int snd_soc_usb_update_offload_route(struct device *dev, int card, int pcm, | ||
184 | + int direction, enum snd_soc_usb_kctl path, | ||
185 | + long *route) | ||
186 | +.. | ||
187 | + | ||
188 | + - ``dev``: USB device to look up offload path mapping | ||
189 | + - ``card``: USB sound card index | ||
190 | + - ``pcm``: USB sound PCM device index | ||
191 | + - ``direction``: direction to fetch offload routing information | ||
192 | + - ``path``: kcontrol selector - pcm device or card index | ||
193 | + - ``route``: mapping of sound card and pcm indexes for the offload path. This is | ||
194 | + an array of two integers that will carry the card and pcm device indexes | ||
195 | + in that specific order. This can be used as the array for the kcontrol | ||
196 | + output. | ||
197 | + | ||
198 | +**snd_soc_usb_update_offload_route()** calls a registered callback to the USB BE DAI | ||
199 | +link to fetch the information about the mapped ASoC devices for executing USB audio | ||
200 | +offload for the device. ``route`` may be a pointer to a kcontrol value output array, | ||
201 | +which carries values when the kcontrol is read. | ||
202 | + | ||
203 | +Returns 0 on success, negative otherwise. | ||
204 | + | ||
205 | +.. code-block:: rst | ||
206 | + | ||
207 | + struct snd_soc_usb *snd_soc_usb_allocate_port(struct snd_soc_component *component, | ||
208 | + void *data); | ||
209 | +.. | ||
210 | + | ||
211 | + - ``component``: DPCM BE DAI link component | ||
212 | + - ``data``: private data | ||
213 | + | ||
214 | +**snd_soc_usb_allocate_port()** allocates a SoC USB device and populates standard | ||
215 | +parameters that is used for further operations. | ||
216 | + | ||
217 | +Returns a pointer to struct soc_usb on success, negative on error. | ||
218 | + | ||
219 | +.. code-block:: rst | ||
220 | + | ||
221 | + void snd_soc_usb_free_port(struct snd_soc_usb *usb); | ||
222 | +.. | ||
223 | + | ||
224 | + - ``usb``: SoC USB device to free | ||
225 | + | ||
226 | +**snd_soc_usb_free_port()** frees a SoC USB device. | ||
227 | + | ||
228 | +.. code-block:: rst | ||
229 | + | ||
230 | + void snd_soc_usb_add_port(struct snd_soc_usb *usb); | ||
231 | +.. | ||
232 | + | ||
233 | + - ``usb``: SoC USB device to add | ||
234 | + | ||
235 | +**snd_soc_usb_add_port()** add an allocated SoC USB device to the SOC USB framework. | ||
236 | +Once added, this device can be referenced by further operations. | ||
237 | + | ||
238 | +.. code-block:: rst | ||
239 | + | ||
240 | + void snd_soc_usb_remove_port(struct snd_soc_usb *usb); | ||
241 | +.. | ||
242 | + | ||
243 | + - ``usb``: SoC USB device to remove | ||
244 | + | ||
245 | +**snd_soc_usb_remove_port()** removes a SoC USB device from the SoC USB framework. | ||
246 | +After removing a device, any SOC USB operations would not be able to reference the | ||
247 | +device removed. | ||
248 | + | ||
249 | +How to Register to SoC USB | ||
250 | +-------------------------- | ||
251 | +The ASoC DPCM USB BE DAI link is the entity responsible for allocating and | ||
252 | +registering the SoC USB device on the component bind. Likewise, it will | ||
253 | +also be responsible for freeing the allocated resources. An example can | ||
254 | +be shown below: | ||
255 | + | ||
256 | +.. code-block:: rst | ||
257 | + | ||
258 | + static int q6usb_component_probe(struct snd_soc_component *component) | ||
259 | + { | ||
260 | + ... | ||
261 | + data->usb = snd_soc_usb_allocate_port(component, 1, &data->priv); | ||
262 | + if (!data->usb) | ||
263 | + return -ENOMEM; | ||
264 | + | ||
265 | + usb->connection_status_cb = q6usb_alsa_connection_cb; | ||
266 | + | ||
267 | + ret = snd_soc_usb_add_port(usb); | ||
268 | + if (ret < 0) { | ||
269 | + dev_err(component->dev, "failed to add usb port\n"); | ||
270 | + goto free_usb; | ||
271 | + } | ||
272 | + ... | ||
273 | + } | ||
274 | + | ||
275 | + static void q6usb_component_remove(struct snd_soc_component *component) | ||
276 | + { | ||
277 | + ... | ||
278 | + snd_soc_usb_remove_port(data->usb); | ||
279 | + snd_soc_usb_free_port(data->usb); | ||
280 | + } | ||
281 | + | ||
282 | + static const struct snd_soc_component_driver q6usb_dai_component = { | ||
283 | + .probe = q6usb_component_probe, | ||
284 | + .remove = q6usb_component_remove, | ||
285 | + .name = "q6usb-dai-component", | ||
286 | + ... | ||
287 | + }; | ||
288 | +.. | ||
289 | + | ||
290 | +BE DAI links can pass along vendor specific information as part of the | ||
291 | +call to allocate the SoC USB device. This will allow any BE DAI link | ||
292 | +parameters or settings to be accessed by the USB offload driver that | ||
293 | +resides in USB SND. | ||
294 | + | ||
295 | +USB Audio Device Connection Flow | ||
296 | +-------------------------------- | ||
297 | +USB devices can be hotplugged into the USB ports at any point in time. | ||
298 | +The BE DAI link should be aware of the current state of the physical USB | ||
299 | +port, i.e. if there are any USB devices with audio interface(s) connected. | ||
300 | +connection_status_cb() can be used to notify the BE DAI link of any change. | ||
301 | + | ||
302 | +This is called whenever there is a USB SND interface bind or remove event, | ||
303 | +using snd_soc_usb_connect() or snd_soc_usb_disconnect(): | ||
304 | + | ||
305 | +.. code-block:: rst | ||
306 | + | ||
307 | + static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) | ||
308 | + { | ||
309 | + ... | ||
310 | + snd_soc_usb_connect(usb_get_usb_backend(udev), sdev); | ||
311 | + ... | ||
312 | + } | ||
313 | + | ||
314 | + static void qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip) | ||
315 | + { | ||
316 | + ... | ||
317 | + snd_soc_usb_disconnect(usb_get_usb_backend(chip->dev), dev->sdev); | ||
318 | + ... | ||
319 | + } | ||
320 | +.. | ||
321 | + | ||
322 | +In order to account for conditions where driver or device existence is | ||
323 | +not guaranteed, USB SND exposes snd_usb_rediscover_devices() to resend the | ||
324 | +connect events for any identified USB audio interfaces. Consider the | ||
325 | +the following situation: | ||
326 | + | ||
327 | + **usb_audio_probe()** | ||
328 | + | --> USB audio streams allocated and saved to usb_chip[] | ||
329 | + | --> Propagate connect event to USB offload driver in USB SND | ||
330 | + | --> **snd_soc_usb_connect()** exits as USB BE DAI link is not ready | ||
331 | + | ||
332 | + BE DAI link component probe | ||
333 | + | --> DAI link is probed and SoC USB port is allocated | ||
334 | + | --> The USB audio device connect event is missed | ||
335 | + | ||
336 | +To ensure connection events are not missed, **snd_usb_rediscover_devices()** | ||
337 | +is executed when the SoC USB device is registered. Now, when the BE DAI | ||
338 | +link component probe occurs, the following highlights the sequence: | ||
339 | + | ||
340 | + BE DAI link component probe | ||
341 | + | --> DAI link is probed and SoC USB port is allocated | ||
342 | + | --> SoC USB device added, and **snd_usb_rediscover_devices()** runs | ||
343 | + | ||
344 | + **snd_usb_rediscover_devices()** | ||
345 | + | --> Traverses through usb_chip[] and for non-NULL entries issue | ||
346 | + | **connection_status_cb()** | ||
347 | + | ||
348 | +In the case where the USB offload driver is unbound, while USB SND is ready, | ||
349 | +the **snd_usb_rediscover_devices()** is called during module init. This allows | ||
350 | +for the offloading path to also be enabled with the following flow: | ||
351 | + | ||
352 | + **usb_audio_probe()** | ||
353 | + | --> USB audio streams allocated and saved to usb_chip[] | ||
354 | + | --> Propagate connect event to USB offload driver in USB SND | ||
355 | + | --> USB offload driver **NOT** ready! | ||
356 | + | ||
357 | + BE DAI link component probe | ||
358 | + | --> DAI link is probed and SoC USB port is allocated | ||
359 | + | --> No USB connect event due to missing USB offload driver | ||
360 | + | ||
361 | + USB offload driver probe | ||
362 | + | --> **qc_usb_audio_offload_init()** | ||
363 | + | --> Calls **snd_usb_rediscover_devices()** to notify of devices | ||
364 | + | ||
365 | +USB Offload Related Kcontrols | ||
366 | +============================= | ||
367 | +Details | ||
368 | +------- | ||
369 | +A set of kcontrols can be utilized by applications to help select the proper sound | ||
370 | +devices to enable USB audio offloading. SoC USB exposes the get_offload_dev() | ||
371 | +callback that designs can use to ensure that the proper indices are returned to the | ||
372 | +application. | ||
373 | + | ||
374 | +Implementation | ||
375 | +-------------- | ||
376 | + | ||
377 | +**Example:** | ||
378 | + | ||
379 | + **Sound Cards**: | ||
380 | + | ||
381 | + :: | ||
382 | + | ||
383 | + 0 [SM8250MTPWCD938]: sm8250 - SM8250-MTP-WCD9380-WSA8810-VA-D | ||
384 | + SM8250-MTP-WCD9380-WSA8810-VA-DMIC | ||
385 | + 1 [Seri ]: USB-Audio - Plantronics Blackwire 3225 Seri | ||
386 | + Plantronics Plantronics Blackwire | ||
387 | + 3225 Seri at usb-xhci-hcd.1.auto-1.1, | ||
388 | + full sp | ||
389 | + 2 [C320M ]: USB-Audio - Plantronics C320-M | ||
390 | + Plantronics Plantronics C320-M at usb-xhci-hcd.1.auto-1.2, full speed | ||
391 | + | ||
392 | + **PCM Devices**: | ||
393 | + | ||
394 | + :: | ||
395 | + | ||
396 | + card 0: SM8250MTPWCD938 [SM8250-MTP-WCD9380-WSA8810-VA-D], device 0: MultiMedia1 (*) [] | ||
397 | + Subdevices: 1/1 | ||
398 | + Subdevice #0: subdevice #0 | ||
399 | + card 0: SM8250MTPWCD938 [SM8250-MTP-WCD9380-WSA8810-VA-D], device 1: MultiMedia2 (*) [] | ||
400 | + Subdevices: 1/1 | ||
401 | + Subdevice #0: subdevice #0 | ||
402 | + card 1: Seri [Plantronics Blackwire 3225 Seri], device 0: USB Audio [USB Audio] | ||
403 | + Subdevices: 1/1 | ||
404 | + Subdevice #0: subdevice #0 | ||
405 | + card 2: C320M [Plantronics C320-M], device 0: USB Audio [USB Audio] | ||
406 | + Subdevices: 1/1 | ||
407 | + Subdevice #0: subdevice #0 | ||
408 | + | ||
409 | + **USB Sound Card** - card#1: | ||
410 | + | ||
411 | + :: | ||
412 | + | ||
413 | + USB Offload Playback Card Route PCM#0 -1 (range -1->32) | ||
414 | + USB Offload Playback PCM Route PCM#0 -1 (range -1->255) | ||
415 | + | ||
416 | + **USB Sound Card** - card#2: | ||
417 | + | ||
418 | + :: | ||
419 | + | ||
420 | + USB Offload Playback Card Route PCM#0 0 (range -1->32) | ||
421 | + USB Offload Playback PCM Route PCM#0 1 (range -1->255) | ||
422 | + | ||
423 | +The above example shows a scenario where the system has one ASoC platform card | ||
424 | +(card#0) and two USB sound devices connected (card#1 and card#2). When reading | ||
425 | +the available kcontrols for each USB audio device, the following kcontrols lists | ||
426 | +the mapped offload card and pcm device indexes for the specific USB device: | ||
427 | + | ||
428 | + ``USB Offload Playback Card Route PCM#*`` | ||
429 | + | ||
430 | + ``USB Offload Playback PCM Route PCM#*`` | ||
431 | + | ||
432 | +The kcontrol is indexed, because a USB audio device could potentially have | ||
433 | +several PCM devices. The above kcontrols are defined as: | ||
434 | + | ||
435 | + - ``USB Offload Playback Card Route PCM#`` **(R)**: Returns the ASoC platform sound | ||
436 | + card index for a mapped offload path. The output **"0"** (card index) signifies | ||
437 | + that there is an available offload path for the USB SND device through card#0. | ||
438 | + If **"-1"** is seen, then no offload path is available for the USB SND device. | ||
439 | + This kcontrol exists for each USB audio device that exists in the system, and | ||
440 | + its expected to derive the current status of offload based on the output value | ||
441 | + for the kcontrol along with the PCM route kcontrol. | ||
442 | + | ||
443 | + - ``USB Offload Playback PCM Route PCM#`` **(R)**: Returns the ASoC platform sound | ||
444 | + PCM device index for a mapped offload path. The output **"1"** (PCM device index) | ||
445 | + signifies that there is an available offload path for the USB SND device through | ||
446 | + PCM device#0. If **"-1"** is seen, then no offload path is available for the USB\ | ||
447 | + SND device. This kcontrol exists for each USB audio device that exists in the | ||
448 | + system, and its expected to derive the current status of offload based on the | ||
449 | + output value for this kcontrol, in addition to the card route kcontrol. | ||
450 | + | ||
451 | +USB Offload Playback Route Kcontrol | ||
452 | +----------------------------------- | ||
453 | +In order to allow for vendor specific implementations on audio offloading device | ||
454 | +selection, the SoC USB layer exposes the following: | ||
455 | + | ||
456 | +.. code-block:: rst | ||
457 | + | ||
458 | + int (*update_offload_route_info)(struct snd_soc_component *component, | ||
459 | + int card, int pcm, int direction, | ||
460 | + enum snd_soc_usb_kctl path, | ||
461 | + long *route) | ||
462 | +.. | ||
463 | + | ||
464 | +These are specific for the **USB Offload Playback Card Route PCM#** and **USB | ||
465 | +Offload PCM Route PCM#** kcontrols. | ||
466 | + | ||
467 | +When users issue get calls to the kcontrol, the registered SoC USB callbacks will | ||
468 | +execute the registered function calls to the DPCM BE DAI link. | ||
469 | + | ||
470 | +**Callback Registration:** | ||
471 | + | ||
472 | +.. code-block:: rst | ||
473 | + | ||
474 | + static int q6usb_component_probe(struct snd_soc_component *component) | ||
475 | + { | ||
476 | + ... | ||
477 | + usb = snd_soc_usb_allocate_port(component, 1, &data->priv); | ||
478 | + if (IS_ERR(usb)) | ||
479 | + return -ENOMEM; | ||
480 | + | ||
481 | + usb->connection_status_cb = q6usb_alsa_connection_cb; | ||
482 | + usb->update_offload_route_info = q6usb_get_offload_dev; | ||
483 | + | ||
484 | + ret = snd_soc_usb_add_port(usb); | ||
485 | +.. | ||
486 | + | ||
487 | +Existing USB Sound Kcontrol | ||
488 | +--------------------------- | ||
489 | +With the introduction of USB offload support, the above USB offload kcontrol | ||
490 | +will be added to the pre existing list of kcontrols identified by the USB sound | ||
491 | +framework. These kcontrols are still the main controls that are used to | ||
492 | +modify characteristics pertaining to the USB audio device. | ||
493 | + | ||
494 | + :: | ||
495 | + | ||
496 | + Number of controls: 9 | ||
497 | + ctl type num name value | ||
498 | + 0 INT 2 Capture Channel Map 0, 0 (range 0->36) | ||
499 | + 1 INT 2 Playback Channel Map 0, 0 (range 0->36) | ||
500 | + 2 BOOL 1 Headset Capture Switch On | ||
501 | + 3 INT 1 Headset Capture Volume 10 (range 0->13) | ||
502 | + 4 BOOL 1 Sidetone Playback Switch On | ||
503 | + 5 INT 1 Sidetone Playback Volume 4096 (range 0->8192) | ||
504 | + 6 BOOL 1 Headset Playback Switch On | ||
505 | + 7 INT 2 Headset Playback Volume 20, 20 (range 0->24) | ||
506 | + 8 INT 1 USB Offload Playback Card Route PCM#0 0 (range -1->32) | ||
507 | + 9 INT 1 USB Offload Playback PCM Route PCM#0 1 (range -1->255) | ||
508 | + | ||
509 | +Since USB audio device controls are handled over the USB control endpoint, use the | ||
510 | +existing mechanisms present in the USB mixer to set parameters, such as volume. | diff view generated by jsdifflib |
1 | Q6DSP supports handling of USB playback audio data if USB audio offloading | 1 | Q6DSP supports handling of USB playback audio data if USB audio offloading |
---|---|---|---|
2 | is enabled. Add a new definition for the USB_RX AFE port, which is | 2 | is enabled. Add a new definition for the USB_RX AFE port, which is |
3 | referenced when the AFE port is started. | 3 | referenced when the AFE port is started. |
4 | 4 | ||
5 | Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> | 5 | Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@linaro.org> |
6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
7 | --- | 7 | --- |
8 | include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h | 1 + | 8 | include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h | 1 + |
9 | 1 file changed, 1 insertion(+) | 9 | 1 file changed, 1 insertion(+) |
10 | 10 | ||
11 | diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h | 11 | diff --git a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h |
12 | index XXXXXXX..XXXXXXX 100644 | 12 | index XXXXXXX..XXXXXXX 100644 |
13 | --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h | 13 | --- a/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h |
14 | +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h | 14 | +++ b/include/dt-bindings/sound/qcom,q6dsp-lpass-ports.h |
15 | @@ -XXX,XX +XXX,XX @@ | 15 | @@ -XXX,XX +XXX,XX @@ |
16 | #define DISPLAY_PORT_RX_5 133 | 16 | #define DISPLAY_PORT_RX_5 133 |
17 | #define DISPLAY_PORT_RX_6 134 | 17 | #define DISPLAY_PORT_RX_6 134 |
18 | #define DISPLAY_PORT_RX_7 135 | 18 | #define DISPLAY_PORT_RX_7 135 |
19 | +#define USB_RX 136 | 19 | +#define USB_RX 136 |
20 | 20 | ||
21 | #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 | 21 | #define LPASS_CLK_ID_PRI_MI2S_IBIT 1 |
22 | #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 | 22 | #define LPASS_CLK_ID_PRI_MI2S_EBIT 2 | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
30 | + platform { | 30 | + platform { |
31 | + sound-dai = <&q6routing>; | 31 | + sound-dai = <&q6routing>; |
32 | + }; | 32 | + }; |
33 | + }; | 33 | + }; |
34 | }; | 34 | }; |
35 | |||
36 | - | | diff view generated by jsdifflib |
1 | The QC ADSP is able to support USB playback endpoints, so that the main | 1 | The QC ADSP is able to support USB playback endpoints, so that the main |
---|---|---|---|
2 | application processor can be placed into lower CPU power modes. This adds | 2 | application processor can be placed into lower CPU power modes. This adds |
3 | the required AFE port configurations and port start command to start an | 3 | the required AFE port configurations and port start command to start an |
4 | audio session. | 4 | audio session. |
5 | 5 | ||
6 | Specifically, the QC ADSP can support all potential endpoints that are | 6 | Specifically, the QC ADSP can support all potential endpoints that are |
7 | exposed by the audio data interface. This includes, feedback endpoints | 7 | exposed by the audio data interface. This includes isochronous data |
8 | (both implicit and explicit) as well as the isochronous (data) endpoints. | 8 | endpoints, in either synchronous mode or asynchronous mode. In the latter |
9 | The size of audio samples sent per USB frame (microframe) will be adjusted | 9 | case both implicit or explicit feedback endpoints are supported. The size |
10 | based on information received on the feedback endpoint. | 10 | of audio samples sent per USB frame (microframe) will be adjusted based on |
11 | information received on the feedback endpoint. | ||
12 | |||
13 | Some pre-requisites are needed before issuing the AFE port start command, | ||
14 | such as setting the USB AFE dev_token. This carries information about the | ||
15 | available USB SND cards and PCM devices that have been discovered on the | ||
16 | USB bus. The dev_token field is used by the audio DSP to notify the USB | ||
17 | offload driver of which card and PCM index to enable playback on. | ||
11 | 18 | ||
12 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 19 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
13 | --- | 20 | --- |
14 | sound/soc/qcom/qdsp6/q6afe-dai.c | 56 +++++++ | 21 | sound/soc/qcom/qdsp6/q6afe-dai.c | 60 +++++++ |
15 | sound/soc/qcom/qdsp6/q6afe.c | 183 ++++++++++++++++++++++- | 22 | sound/soc/qcom/qdsp6/q6afe.c | 192 ++++++++++++++++++++++- |
16 | sound/soc/qcom/qdsp6/q6afe.h | 35 ++++- | 23 | sound/soc/qcom/qdsp6/q6afe.h | 36 ++++- |
17 | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 23 +++ | 24 | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 23 +++ |
18 | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h | 1 + | 25 | sound/soc/qcom/qdsp6/q6dsp-lpass-ports.h | 1 + |
19 | sound/soc/qcom/qdsp6/q6routing.c | 9 ++ | 26 | sound/soc/qcom/qdsp6/q6routing.c | 32 +++- |
20 | 6 files changed, 305 insertions(+), 2 deletions(-) | 27 | 6 files changed, 341 insertions(+), 3 deletions(-) |
21 | 28 | ||
22 | diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c | 29 | diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c |
23 | index XXXXXXX..XXXXXXX 100644 | 30 | index XXXXXXX..XXXXXXX 100644 |
24 | --- a/sound/soc/qcom/qdsp6/q6afe-dai.c | 31 | --- a/sound/soc/qcom/qdsp6/q6afe-dai.c |
25 | +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c | 32 | +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c |
26 | @@ -XXX,XX +XXX,XX @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream, | 33 | @@ -XXX,XX +XXX,XX @@ static int q6hdmi_hw_params(struct snd_pcm_substream *substream, |
27 | return 0; | 34 | return 0; |
28 | } | 35 | } |
29 | 36 | ||
30 | +static int q6usb_hw_params(struct snd_pcm_substream *substream, | 37 | +static int q6afe_usb_hw_params(struct snd_pcm_substream *substream, |
31 | + struct snd_pcm_hw_params *params, | 38 | + struct snd_pcm_hw_params *params, |
32 | + struct snd_soc_dai *dai) | 39 | + struct snd_soc_dai *dai) |
33 | +{ | 40 | +{ |
34 | + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); | 41 | + struct q6afe_dai_data *dai_data = dev_get_drvdata(dai->dev); |
35 | + int channels = params_channels(params); | 42 | + int channels = params_channels(params); |
36 | + int rate = params_rate(params); | 43 | + int rate = params_rate(params); |
37 | + struct q6afe_usb_cfg *usb = &dai_data->port_config[dai->id].usb_audio; | 44 | + struct q6afe_usb_cfg *usb = &dai_data->port_config[dai->id].usb_audio; |
... | ... | ||
40 | + usb->num_channels = channels; | 47 | + usb->num_channels = channels; |
41 | + | 48 | + |
42 | + switch (params_format(params)) { | 49 | + switch (params_format(params)) { |
43 | + case SNDRV_PCM_FORMAT_U16_LE: | 50 | + case SNDRV_PCM_FORMAT_U16_LE: |
44 | + case SNDRV_PCM_FORMAT_S16_LE: | 51 | + case SNDRV_PCM_FORMAT_S16_LE: |
45 | + case SNDRV_PCM_FORMAT_SPECIAL: | ||
46 | + usb->bit_width = 16; | 52 | + usb->bit_width = 16; |
47 | + break; | 53 | + break; |
48 | + case SNDRV_PCM_FORMAT_S24_LE: | 54 | + case SNDRV_PCM_FORMAT_S24_LE: |
49 | + case SNDRV_PCM_FORMAT_S24_3LE: | 55 | + case SNDRV_PCM_FORMAT_S24_3LE: |
50 | + usb->bit_width = 24; | 56 | + usb->bit_width = 24; |
... | ... | ||
87 | static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) | 93 | static int msm_dai_q6_dai_probe(struct snd_soc_dai *dai) |
88 | @@ -XXX,XX +XXX,XX @@ static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) | 94 | @@ -XXX,XX +XXX,XX @@ static int msm_dai_q6_dai_remove(struct snd_soc_dai *dai) |
89 | return 0; | 95 | return 0; |
90 | } | 96 | } |
91 | 97 | ||
92 | +static const struct snd_soc_dai_ops q6usb_ops = { | 98 | +static const struct snd_soc_dai_ops q6afe_usb_ops = { |
93 | + .probe = msm_dai_q6_dai_probe, | 99 | + .probe = msm_dai_q6_dai_probe, |
94 | + .prepare = q6afe_dai_prepare, | 100 | + .prepare = q6afe_dai_prepare, |
95 | + .hw_params = q6usb_hw_params, | 101 | + .hw_params = q6afe_usb_hw_params, |
102 | + /* | ||
103 | + * Shutdown callback required to stop the USB AFE port, which is enabled | ||
104 | + * by the prepare() stage. This stops the audio traffic on the USB AFE | ||
105 | + * port on the Q6DSP. | ||
106 | + */ | ||
96 | + .shutdown = q6afe_dai_shutdown, | 107 | + .shutdown = q6afe_dai_shutdown, |
97 | + /* | 108 | + /* |
98 | + * Startup callback not needed, as AFE port start command passes the PCM | 109 | + * Startup callback not needed, as AFE port start command passes the PCM |
99 | + * parameters within the AFE command, which is provided by the PCM core | 110 | + * parameters within the AFE command, which is provided by the PCM core |
100 | + * during the prepare() stage. | 111 | + * during the prepare() stage. |
... | ... | ||
115 | static const struct snd_soc_component_driver q6afe_dai_component = { | 126 | static const struct snd_soc_component_driver q6afe_dai_component = { |
116 | @@ -XXX,XX +XXX,XX @@ static int q6afe_dai_dev_probe(struct platform_device *pdev) | 127 | @@ -XXX,XX +XXX,XX @@ static int q6afe_dai_dev_probe(struct platform_device *pdev) |
117 | cfg.q6i2s_ops = &q6i2s_ops; | 128 | cfg.q6i2s_ops = &q6i2s_ops; |
118 | cfg.q6tdm_ops = &q6tdm_ops; | 129 | cfg.q6tdm_ops = &q6tdm_ops; |
119 | cfg.q6dma_ops = &q6dma_ops; | 130 | cfg.q6dma_ops = &q6dma_ops; |
120 | + cfg.q6usb_ops = &q6usb_ops; | 131 | + cfg.q6usb_ops = &q6afe_usb_ops; |
121 | dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); | 132 | dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); |
122 | 133 | ||
123 | return devm_snd_soc_register_component(dev, &q6afe_dai_component, dais, num_dais); | 134 | return devm_snd_soc_register_component(dev, &q6afe_dai_component, dais, num_dais); |
124 | diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c | 135 | diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c |
125 | index XXXXXXX..XXXXXXX 100644 | 136 | index XXXXXXX..XXXXXXX 100644 |
... | ... | ||
229 | +struct afe_param_id_usb_audio_dev_lpcm_fmt { | 240 | +struct afe_param_id_usb_audio_dev_lpcm_fmt { |
230 | + u32 cfg_minor_version; | 241 | + u32 cfg_minor_version; |
231 | + u32 endian; | 242 | + u32 endian; |
232 | +} __packed; | 243 | +} __packed; |
233 | + | 244 | + |
234 | +/** | ||
235 | + * struct afe_param_id_usb_audio_dev_latency_mode | ||
236 | + * @cfg_minor_version: Minor version used for tracking USB audio device | ||
237 | + * configuration. | ||
238 | + * Supported values: | ||
239 | + * AFE_API_MINOR_VERSION_USB_AUDIO_LATENCY_MODE | ||
240 | + * @mode: latency mode for the USB audio device | ||
241 | + **/ | ||
242 | +struct afe_param_id_usb_audio_dev_latency_mode { | ||
243 | + u32 minor_version; | ||
244 | + u32 mode; | ||
245 | +} __packed; | ||
246 | + | ||
247 | +#define AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL 0x000102B7 | 245 | +#define AFE_PARAM_ID_USB_AUDIO_SVC_INTERVAL 0x000102B7 |
248 | + | 246 | + |
249 | +/** | 247 | +/** |
250 | + * struct afe_param_id_usb_audio_svc_interval | 248 | + * struct afe_param_id_usb_audio_svc_interval |
251 | + * @cfg_minor_version: Minor version used for tracking USB audio device | 249 | + * @cfg_minor_version: Minor version used for tracking USB audio device |
... | ... | ||
279 | static void q6afe_port_free(struct kref *ref) | 277 | static void q6afe_port_free(struct kref *ref) |
280 | @@ -XXX,XX +XXX,XX @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, | 278 | @@ -XXX,XX +XXX,XX @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, |
281 | } | 279 | } |
282 | EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare); | 280 | EXPORT_SYMBOL_GPL(q6afe_tdm_port_prepare); |
283 | 281 | ||
284 | +static int afe_port_send_usb_dev_param(struct q6afe_port *port, struct q6afe_usb_cfg *cfg) | 282 | +/** |
283 | + * afe_port_send_usb_dev_param() - Send USB dev token | ||
284 | + * | ||
285 | + * @port: Instance of afe port | ||
286 | + * @cardidx: USB SND card index to reference | ||
287 | + * @pcmidx: USB SND PCM device index to reference | ||
288 | + * | ||
289 | + * The USB dev token carries information about which USB SND card instance and | ||
290 | + * PCM device to execute the offload on. This information is carried through | ||
291 | + * to the stream enable QMI request, which is handled by the offload class | ||
292 | + * driver. The information is parsed to determine which USB device to query | ||
293 | + * the required resources for. | ||
294 | + */ | ||
295 | +int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx) | ||
296 | +{ | ||
297 | + struct afe_param_id_usb_audio_dev_params usb_dev; | ||
298 | + int ret; | ||
299 | + | ||
300 | + memset(&usb_dev, 0, sizeof(usb_dev)); | ||
301 | + | ||
302 | + usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; | ||
303 | + usb_dev.dev_token = (cardidx << 16) | (pcmidx << 8); | ||
304 | + ret = q6afe_port_set_param_v2(port, &usb_dev, | ||
305 | + AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS, | ||
306 | + AFE_MODULE_AUDIO_DEV_INTERFACE, | ||
307 | + sizeof(usb_dev)); | ||
308 | + if (ret) | ||
309 | + dev_err(port->afe->dev, "%s: AFE device param cmd failed %d\n", | ||
310 | + __func__, ret); | ||
311 | + | ||
312 | + return ret; | ||
313 | +} | ||
314 | +EXPORT_SYMBOL_GPL(afe_port_send_usb_dev_param); | ||
315 | + | ||
316 | +static int afe_port_send_usb_params(struct q6afe_port *port, struct q6afe_usb_cfg *cfg) | ||
285 | +{ | 317 | +{ |
286 | + union afe_port_config *pcfg = &port->port_cfg; | 318 | + union afe_port_config *pcfg = &port->port_cfg; |
287 | + struct afe_param_id_usb_audio_dev_params usb_dev; | ||
288 | + struct afe_param_id_usb_audio_dev_lpcm_fmt lpcm_fmt; | 319 | + struct afe_param_id_usb_audio_dev_lpcm_fmt lpcm_fmt; |
289 | + struct afe_param_id_usb_audio_svc_interval svc_int; | 320 | + struct afe_param_id_usb_audio_svc_interval svc_int; |
290 | + int ret = 0; | 321 | + int ret; |
291 | + | 322 | + |
292 | + if (!pcfg) { | 323 | + if (!pcfg) { |
293 | + dev_err(port->afe->dev, "%s: Error, no configuration data\n", __func__); | 324 | + dev_err(port->afe->dev, "%s: Error, no configuration data\n", __func__); |
294 | + ret = -EINVAL; | 325 | + ret = -EINVAL; |
295 | + goto exit; | 326 | + goto exit; |
296 | + } | 327 | + } |
297 | + | 328 | + |
298 | + memset(&usb_dev, 0, sizeof(usb_dev)); | ||
299 | + memset(&lpcm_fmt, 0, sizeof(lpcm_fmt)); | 329 | + memset(&lpcm_fmt, 0, sizeof(lpcm_fmt)); |
300 | + memset(&svc_int, 0, sizeof(svc_int)); | 330 | + memset(&svc_int, 0, sizeof(svc_int)); |
301 | + | ||
302 | + usb_dev.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; | ||
303 | + ret = q6afe_port_set_param_v2(port, &usb_dev, | ||
304 | + AFE_PARAM_ID_USB_AUDIO_DEV_PARAMS, | ||
305 | + AFE_MODULE_AUDIO_DEV_INTERFACE, sizeof(usb_dev)); | ||
306 | + if (ret) { | ||
307 | + dev_err(port->afe->dev, "%s: AFE device param cmd failed %d\n", | ||
308 | + __func__, ret); | ||
309 | + goto exit; | ||
310 | + } | ||
311 | + | 331 | + |
312 | + lpcm_fmt.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; | 332 | + lpcm_fmt.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; |
313 | + lpcm_fmt.endian = pcfg->usb_cfg.endian; | 333 | + lpcm_fmt.endian = pcfg->usb_cfg.endian; |
314 | + ret = q6afe_port_set_param_v2(port, &lpcm_fmt, | 334 | + ret = q6afe_port_set_param_v2(port, &lpcm_fmt, |
315 | + AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT, | 335 | + AFE_PARAM_ID_USB_AUDIO_DEV_LPCM_FMT, |
... | ... | ||
339 | + * @port: Instance of afe port | 359 | + * @port: Instance of afe port |
340 | + * @cfg: USB configuration for the afe port | 360 | + * @cfg: USB configuration for the afe port |
341 | + * | 361 | + * |
342 | + */ | 362 | + */ |
343 | +void q6afe_usb_port_prepare(struct q6afe_port *port, | 363 | +void q6afe_usb_port_prepare(struct q6afe_port *port, |
344 | + struct q6afe_usb_cfg *cfg) | 364 | + struct q6afe_usb_cfg *cfg) |
345 | +{ | 365 | +{ |
346 | + union afe_port_config *pcfg = &port->port_cfg; | 366 | + union afe_port_config *pcfg = &port->port_cfg; |
347 | + | 367 | + |
348 | + pcfg->usb_cfg.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; | 368 | + pcfg->usb_cfg.cfg_minor_version = AFE_API_MINOR_VERSION_USB_AUDIO_CONFIG; |
349 | + pcfg->usb_cfg.sample_rate = cfg->sample_rate; | 369 | + pcfg->usb_cfg.sample_rate = cfg->sample_rate; |
350 | + pcfg->usb_cfg.num_channels = cfg->num_channels; | 370 | + pcfg->usb_cfg.num_channels = cfg->num_channels; |
351 | + pcfg->usb_cfg.bit_width = cfg->bit_width; | 371 | + pcfg->usb_cfg.bit_width = cfg->bit_width; |
352 | + | 372 | + |
353 | + afe_port_send_usb_dev_param(port, cfg); | 373 | + afe_port_send_usb_params(port, cfg); |
354 | +} | 374 | +} |
355 | +EXPORT_SYMBOL_GPL(q6afe_usb_port_prepare); | 375 | +EXPORT_SYMBOL_GPL(q6afe_usb_port_prepare); |
356 | + | 376 | + |
357 | /** | 377 | /** |
358 | * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. | 378 | * q6afe_hdmi_port_prepare() - Prepare hdmi afe port. |
... | ... | ||
372 | diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h | 392 | diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h |
373 | index XXXXXXX..XXXXXXX 100644 | 393 | index XXXXXXX..XXXXXXX 100644 |
374 | --- a/sound/soc/qcom/qdsp6/q6afe.h | 394 | --- a/sound/soc/qcom/qdsp6/q6afe.h |
375 | +++ b/sound/soc/qcom/qdsp6/q6afe.h | 395 | +++ b/sound/soc/qcom/qdsp6/q6afe.h |
376 | @@ -XXX,XX +XXX,XX @@ | 396 | @@ -XXX,XX +XXX,XX @@ |
377 | 397 | #ifndef __Q6AFE_H__ | |
378 | #include <dt-bindings/sound/qcom,q6afe.h> | 398 | #define __Q6AFE_H__ |
379 | 399 | ||
380 | -#define AFE_PORT_MAX 129 | 400 | -#define AFE_PORT_MAX 129 |
381 | +#define AFE_PORT_MAX 137 | 401 | +#define AFE_PORT_MAX 137 |
382 | 402 | ||
383 | #define MSM_AFE_PORT_TYPE_RX 0 | 403 | #define MSM_AFE_PORT_TYPE_RX 0 |
... | ... | ||
430 | @@ -XXX,XX +XXX,XX @@ int q6afe_port_start(struct q6afe_port *port); | 450 | @@ -XXX,XX +XXX,XX @@ int q6afe_port_start(struct q6afe_port *port); |
431 | int q6afe_port_stop(struct q6afe_port *port); | 451 | int q6afe_port_stop(struct q6afe_port *port); |
432 | void q6afe_port_put(struct q6afe_port *port); | 452 | void q6afe_port_put(struct q6afe_port *port); |
433 | int q6afe_get_port_id(int index); | 453 | int q6afe_get_port_id(int index); |
434 | +void q6afe_usb_port_prepare(struct q6afe_port *port, | 454 | +void q6afe_usb_port_prepare(struct q6afe_port *port, |
435 | + struct q6afe_usb_cfg *cfg); | 455 | + struct q6afe_usb_cfg *cfg); |
436 | void q6afe_hdmi_port_prepare(struct q6afe_port *port, | 456 | void q6afe_hdmi_port_prepare(struct q6afe_port *port, |
437 | struct q6afe_hdmi_cfg *cfg); | 457 | struct q6afe_hdmi_cfg *cfg); |
438 | void q6afe_slim_port_prepare(struct q6afe_port *port, | 458 | void q6afe_slim_port_prepare(struct q6afe_port *port, |
459 | @@ -XXX,XX +XXX,XX @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, struct q6afe_tdm_cfg *cfg); | ||
460 | void q6afe_cdc_dma_port_prepare(struct q6afe_port *port, | ||
461 | struct q6afe_cdc_dma_cfg *cfg); | ||
462 | |||
463 | +int afe_port_send_usb_dev_param(struct q6afe_port *port, int cardidx, int pcmidx); | ||
464 | int q6afe_port_set_sysclk(struct q6afe_port *port, int clk_id, | ||
465 | int clk_src, int clk_root, | ||
466 | unsigned int freq, int dir); | ||
439 | diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 467 | diff --git a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c |
440 | index XXXXXXX..XXXXXXX 100644 | 468 | index XXXXXXX..XXXXXXX 100644 |
441 | --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 469 | --- a/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c |
442 | +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c | 470 | +++ b/sound/soc/qcom/qdsp6/q6dsp-lpass-ports.c |
443 | @@ -XXX,XX +XXX,XX @@ | 471 | @@ -XXX,XX +XXX,XX @@ |
... | ... | ||
491 | struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, | 519 | struct snd_soc_dai_driver *q6dsp_audio_ports_set_config(struct device *dev, |
492 | diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c | 520 | diff --git a/sound/soc/qcom/qdsp6/q6routing.c b/sound/soc/qcom/qdsp6/q6routing.c |
493 | index XXXXXXX..XXXXXXX 100644 | 521 | index XXXXXXX..XXXXXXX 100644 |
494 | --- a/sound/soc/qcom/qdsp6/q6routing.c | 522 | --- a/sound/soc/qcom/qdsp6/q6routing.c |
495 | +++ b/sound/soc/qcom/qdsp6/q6routing.c | 523 | +++ b/sound/soc/qcom/qdsp6/q6routing.c |
524 | @@ -XXX,XX +XXX,XX @@ static struct session_data *get_session_from_id(struct msm_routing_data *data, | ||
525 | |||
526 | return NULL; | ||
527 | } | ||
528 | + | ||
529 | +static bool is_usb_routing_enabled(struct msm_routing_data *data) | ||
530 | +{ | ||
531 | + int i; | ||
532 | + | ||
533 | + /* | ||
534 | + * Loop through current sessions to see if there are active routes | ||
535 | + * to the USB_RX backend DAI. The USB offload routing is designed | ||
536 | + * similarly to the non offload path. If there are multiple PCM | ||
537 | + * devices associated with the ASoC platform card, only one active | ||
538 | + * path can be routed to the USB offloaded endpoint. | ||
539 | + */ | ||
540 | + for (i = 0; i < MAX_SESSIONS; i++) { | ||
541 | + if (data->sessions[i].port_id == USB_RX) | ||
542 | + return true; | ||
543 | + } | ||
544 | + | ||
545 | + return false; | ||
546 | +} | ||
547 | + | ||
548 | /** | ||
549 | * q6routing_stream_close() - Deregister a stream | ||
550 | * | ||
551 | @@ -XXX,XX +XXX,XX @@ static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, | ||
552 | struct session_data *session = &data->sessions[session_id]; | ||
553 | |||
554 | if (ucontrol->value.integer.value[0]) { | ||
555 | - if (session->port_id == be_id) | ||
556 | + if (session->port_id == be_id || | ||
557 | + (be_id == USB_RX && is_usb_routing_enabled(data))) | ||
558 | return 0; | ||
559 | |||
560 | session->port_id = be_id; | ||
496 | @@ -XXX,XX +XXX,XX @@ static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, | 561 | @@ -XXX,XX +XXX,XX @@ static int msm_routing_put_audio_mixer(struct snd_kcontrol *kcontrol, |
497 | return 1; | 562 | return 1; |
498 | } | 563 | } |
499 | 564 | ||
500 | +static const struct snd_kcontrol_new usb_mixer_controls[] = { | 565 | +static const struct snd_kcontrol_new usb_mixer_controls[] = { |
... | ... | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
3 | the USB bus is suspended, this routine would require more time to complete, | 3 | the USB bus is suspended, this routine would require more time to complete, |
4 | as resuming the USB bus has some overhead associated with it. Increase the | 4 | as resuming the USB bus has some overhead associated with it. Increase the |
5 | timeout to 3s to allow for sufficient time for the USB QMI stream enable | 5 | timeout to 3s to allow for sufficient time for the USB QMI stream enable |
6 | handshake to complete. | 6 | handshake to complete. |
7 | 7 | ||
8 | Reviewed-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com> | ||
8 | Reviewed-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> | 9 | Reviewed-by: Srinivas Kandagatla <srinivas.kandagatla@linaro.org> |
9 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 10 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
10 | --- | 11 | --- |
11 | sound/soc/qcom/qdsp6/q6afe.c | 2 +- | 12 | sound/soc/qcom/qdsp6/q6afe.c | 2 +- |
12 | 1 file changed, 1 insertion(+), 1 deletion(-) | 13 | 1 file changed, 1 insertion(+), 1 deletion(-) |
... | ... | diff view generated by jsdifflib |
1 | Create a USB BE component that will register a new USB port to the ASoC USB | 1 | Create a USB BE component that will register a new USB port to the ASoC USB |
---|---|---|---|
2 | framework. This will handle determination on if the requested audio | 2 | framework. This will handle determination on if the requested audio |
3 | profile is supported by the USB device currently selected. | 3 | profile is supported by the USB device currently selected. |
4 | 4 | ||
5 | Check for if the PCM format is supported during the hw_params callback. If | ||
6 | the profile is not supported then the userspace ALSA entity will receive an | ||
7 | error, and can take further action. | ||
8 | |||
5 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 9 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
6 | --- | 10 | --- |
7 | include/sound/q6usboffload.h | 20 ++++ | 11 | include/sound/q6usboffload.h | 20 +++ |
8 | sound/soc/qcom/Kconfig | 4 + | 12 | sound/soc/qcom/Kconfig | 10 ++ |
9 | sound/soc/qcom/qdsp6/Makefile | 1 + | 13 | sound/soc/qcom/qdsp6/Makefile | 1 + |
10 | sound/soc/qcom/qdsp6/q6usb.c | 200 ++++++++++++++++++++++++++++++++++ | 14 | sound/soc/qcom/qdsp6/q6usb.c | 246 ++++++++++++++++++++++++++++++++++ |
11 | 4 files changed, 225 insertions(+) | 15 | 4 files changed, 277 insertions(+) |
12 | create mode 100644 include/sound/q6usboffload.h | 16 | create mode 100644 include/sound/q6usboffload.h |
13 | create mode 100644 sound/soc/qcom/qdsp6/q6usb.c | 17 | create mode 100644 sound/soc/qcom/qdsp6/q6usb.c |
14 | 18 | ||
15 | diff --git a/include/sound/q6usboffload.h b/include/sound/q6usboffload.h | 19 | diff --git a/include/sound/q6usboffload.h b/include/sound/q6usboffload.h |
16 | new file mode 100644 | 20 | new file mode 100644 |
17 | index XXXXXXX..XXXXXXX | 21 | index XXXXXXX..XXXXXXX |
18 | --- /dev/null | 22 | --- /dev/null |
19 | +++ b/include/sound/q6usboffload.h | 23 | +++ b/include/sound/q6usboffload.h |
20 | @@ -XXX,XX +XXX,XX @@ | 24 | @@ -XXX,XX +XXX,XX @@ |
21 | +/* SPDX-License-Identifier: GPL-2.0 | 25 | +/* SPDX-License-Identifier: GPL-2.0 |
22 | + * | 26 | + * |
23 | + * linux/sound/q6usboffload.h -- QDSP6 USB offload | 27 | + * sound/q6usboffload.h -- QDSP6 USB offload |
24 | + * | 28 | + * |
25 | + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. | 29 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. |
26 | + */ | 30 | + */ |
27 | + | 31 | + |
28 | +/** | 32 | +/** |
29 | + * struct q6usb_offload | 33 | + * struct q6usb_offload - USB backend DAI link offload parameters |
30 | + * @dev - dev handle to usb be | 34 | + * @dev: dev handle to usb be |
31 | + * @sid - streamID for iommu | 35 | + * @domain: allocated iommu domain |
32 | + * @intr_num - usb interrupter number | 36 | + * @sid: streamID for iommu |
33 | + * @domain - allocated iommu domain | 37 | + * @intr_num: usb interrupter number |
34 | + **/ | 38 | + **/ |
35 | +struct q6usb_offload { | 39 | +struct q6usb_offload { |
36 | + struct device *dev; | 40 | + struct device *dev; |
41 | + struct iommu_domain *domain; | ||
37 | + long long sid; | 42 | + long long sid; |
38 | + u16 intr_num; | 43 | + u16 intr_num; |
39 | + struct iommu_domain *domain; | ||
40 | +}; | 44 | +}; |
41 | diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig | 45 | diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig |
42 | index XXXXXXX..XXXXXXX 100644 | 46 | index XXXXXXX..XXXXXXX 100644 |
43 | --- a/sound/soc/qcom/Kconfig | 47 | --- a/sound/soc/qcom/Kconfig |
44 | +++ b/sound/soc/qcom/Kconfig | 48 | +++ b/sound/soc/qcom/Kconfig |
45 | @@ -XXX,XX +XXX,XX @@ config SND_SOC_QDSP6_APM | 49 | @@ -XXX,XX +XXX,XX @@ config SND_SOC_QDSP6_PRM |
46 | config SND_SOC_QDSP6_PRM_LPASS_CLOCKS | ||
47 | tristate | ||
48 | |||
49 | +config SND_SOC_QDSP6_USB | ||
50 | + tristate | ||
51 | + | ||
52 | config SND_SOC_QDSP6_PRM | ||
53 | tristate | 50 | tristate |
54 | select SND_SOC_QDSP6_PRM_LPASS_CLOCKS | 51 | select SND_SOC_QDSP6_PRM_LPASS_CLOCKS |
55 | @@ -XXX,XX +XXX,XX @@ config SND_SOC_QDSP6 | 52 | |
56 | select SND_SOC_TOPOLOGY | 53 | +config SND_SOC_QDSP6_USB |
57 | select SND_SOC_QDSP6_APM | 54 | + tristate "SoC ALSA USB offloading backing for QDSP6" |
58 | select SND_SOC_QDSP6_PRM | 55 | + depends on SND_SOC_USB |
59 | + select SND_SOC_QDSP6_USB | 56 | + help |
60 | help | 57 | + Adds support for USB offloading for QDSP6 ASoC |
61 | To add support for MSM QDSP6 Soc Audio. | 58 | + based platform sound cards. This will enable the |
62 | This will enable sound soc platform specific | 59 | + Q6USB DPCM backend DAI link, which will interact |
60 | + with the SoC USB framework to initialize a session | ||
61 | + with active USB SND devices. | ||
62 | + | ||
63 | config SND_SOC_QDSP6 | ||
64 | tristate "SoC ALSA audio driver for QDSP6" | ||
65 | depends on QCOM_APR | ||
63 | diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile | 66 | diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile |
64 | index XXXXXXX..XXXXXXX 100644 | 67 | index XXXXXXX..XXXXXXX 100644 |
65 | --- a/sound/soc/qcom/qdsp6/Makefile | 68 | --- a/sound/soc/qcom/qdsp6/Makefile |
66 | +++ b/sound/soc/qcom/qdsp6/Makefile | 69 | +++ b/sound/soc/qcom/qdsp6/Makefile |
67 | @@ -XXX,XX +XXX,XX @@ obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o | 70 | @@ -XXX,XX +XXX,XX @@ obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o |
... | ... | ||
75 | --- /dev/null | 78 | --- /dev/null |
76 | +++ b/sound/soc/qcom/qdsp6/q6usb.c | 79 | +++ b/sound/soc/qcom/qdsp6/q6usb.c |
77 | @@ -XXX,XX +XXX,XX @@ | 80 | @@ -XXX,XX +XXX,XX @@ |
78 | +// SPDX-License-Identifier: GPL-2.0 | 81 | +// SPDX-License-Identifier: GPL-2.0 |
79 | +/* | 82 | +/* |
80 | + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. | 83 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. |
81 | + */ | 84 | + */ |
82 | + | 85 | + |
86 | +#include <linux/device.h> | ||
87 | +#include <linux/dma-mapping.h> | ||
88 | +#include <linux/dma-map-ops.h> | ||
83 | +#include <linux/err.h> | 89 | +#include <linux/err.h> |
84 | +#include <linux/init.h> | 90 | +#include <linux/init.h> |
91 | +#include <linux/iommu.h> | ||
85 | +#include <linux/module.h> | 92 | +#include <linux/module.h> |
86 | +#include <linux/device.h> | ||
87 | +#include <linux/platform_device.h> | 93 | +#include <linux/platform_device.h> |
88 | +#include <linux/slab.h> | 94 | +#include <linux/slab.h> |
89 | +#include <linux/iommu.h> | 95 | + |
90 | +#include <linux/dma-mapping.h> | 96 | +#include <sound/asound.h> |
91 | +#include <linux/dma-map-ops.h> | ||
92 | + | ||
93 | +#include <sound/pcm.h> | 97 | +#include <sound/pcm.h> |
98 | +#include <sound/pcm_params.h> | ||
99 | +#include <sound/q6usboffload.h> | ||
94 | +#include <sound/soc.h> | 100 | +#include <sound/soc.h> |
95 | +#include <sound/soc-usb.h> | 101 | +#include <sound/soc-usb.h> |
96 | +#include <sound/pcm_params.h> | 102 | + |
97 | +#include <sound/asound.h> | 103 | +#include <dt-bindings/sound/qcom,q6afe.h> |
98 | +#include <sound/q6usboffload.h> | 104 | + |
99 | + | 105 | +#include "q6afe.h" |
100 | +#include "q6dsp-lpass-ports.h" | 106 | +#include "q6dsp-lpass-ports.h" |
101 | +#include "q6afe.h" | 107 | + |
102 | + | 108 | +#define Q6_USB_SID_MASK 0xF |
103 | +#define SID_MASK 0xF | ||
104 | + | 109 | + |
105 | +struct q6usb_port_data { | 110 | +struct q6usb_port_data { |
106 | + struct q6afe_usb_cfg usb_cfg; | 111 | + struct q6afe_usb_cfg usb_cfg; |
107 | + struct snd_soc_usb *usb; | 112 | + struct snd_soc_usb *usb; |
108 | + struct q6usb_offload priv; | 113 | + struct q6usb_offload priv; |
109 | + int active_idx; | 114 | + |
115 | + /* Protects against operations between SOC USB and ASoC */ | ||
116 | + struct mutex mutex; | ||
117 | + struct list_head devices; | ||
110 | +}; | 118 | +}; |
111 | + | 119 | + |
112 | +static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = { | 120 | +static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = { |
113 | + SND_SOC_DAPM_HP("USB_RX_BE", NULL), | 121 | + SND_SOC_DAPM_HP("USB_RX_BE", NULL), |
114 | +}; | 122 | +}; |
... | ... | ||
119 | + | 127 | + |
120 | +static int q6usb_hw_params(struct snd_pcm_substream *substream, | 128 | +static int q6usb_hw_params(struct snd_pcm_substream *substream, |
121 | + struct snd_pcm_hw_params *params, | 129 | + struct snd_pcm_hw_params *params, |
122 | + struct snd_soc_dai *dai) | 130 | + struct snd_soc_dai *dai) |
123 | +{ | 131 | +{ |
124 | + return 0; | 132 | + struct q6usb_port_data *data = dev_get_drvdata(dai->dev); |
133 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
134 | + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | ||
135 | + int direction = substream->stream; | ||
136 | + struct q6afe_port *q6usb_afe; | ||
137 | + struct snd_soc_usb_device *sdev; | ||
138 | + int ret = -EINVAL; | ||
139 | + | ||
140 | + mutex_lock(&data->mutex); | ||
141 | + | ||
142 | + /* No active chip index */ | ||
143 | + if (list_empty(&data->devices)) | ||
144 | + goto out; | ||
145 | + | ||
146 | + sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); | ||
147 | + | ||
148 | + ret = snd_soc_usb_find_supported_format(sdev->chip_idx, params, direction); | ||
149 | + if (ret < 0) | ||
150 | + goto out; | ||
151 | + | ||
152 | + q6usb_afe = q6afe_port_get_from_id(cpu_dai->dev, USB_RX); | ||
153 | + if (IS_ERR(q6usb_afe)) | ||
154 | + goto out; | ||
155 | + | ||
156 | + /* Notify audio DSP about the devices being offloaded */ | ||
157 | + ret = afe_port_send_usb_dev_param(q6usb_afe, sdev->card_idx, | ||
158 | + sdev->ppcm_idx[sdev->num_playback - 1]); | ||
159 | + | ||
160 | +out: | ||
161 | + mutex_unlock(&data->mutex); | ||
162 | + | ||
163 | + return ret; | ||
125 | +} | 164 | +} |
126 | + | 165 | + |
127 | +static const struct snd_soc_dai_ops q6usb_ops = { | 166 | +static const struct snd_soc_dai_ops q6usb_ops = { |
128 | + .hw_params = q6usb_hw_params, | 167 | + .hw_params = q6usb_hw_params, |
129 | +}; | 168 | +}; |
... | ... | ||
151 | + .ops = &q6usb_ops, | 190 | + .ops = &q6usb_ops, |
152 | + }, | 191 | + }, |
153 | +}; | 192 | +}; |
154 | + | 193 | + |
155 | +static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, | 194 | +static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, |
156 | + const struct of_phandle_args *args, | 195 | + const struct of_phandle_args *args, |
157 | + const char **dai_name) | 196 | + const char **dai_name) |
158 | +{ | 197 | +{ |
159 | + int id = args->args[0]; | 198 | + int id = args->args[0]; |
160 | + int ret = -EINVAL; | 199 | + int ret = -EINVAL; |
161 | + int i; | 200 | + int i; |
162 | + | 201 | + |
... | ... | ||
170 | + | 209 | + |
171 | + return ret; | 210 | + return ret; |
172 | +} | 211 | +} |
173 | + | 212 | + |
174 | +static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, | 213 | +static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, |
175 | + struct snd_soc_usb_device *sdev, bool connected) | 214 | + struct snd_soc_usb_device *sdev, bool connected) |
176 | +{ | 215 | +{ |
177 | + struct q6usb_port_data *data; | 216 | + struct q6usb_port_data *data; |
178 | + | 217 | + |
179 | + if (!usb->component) | 218 | + if (!usb->component) |
180 | + return -ENODEV; | 219 | + return -ENODEV; |
181 | + | 220 | + |
182 | + data = dev_get_drvdata(usb->component->dev); | 221 | + data = dev_get_drvdata(usb->component->dev); |
183 | + | 222 | + |
223 | + mutex_lock(&data->mutex); | ||
184 | + if (connected) { | 224 | + if (connected) { |
185 | + /* We only track the latest USB headset plugged in */ | 225 | + /* Selects the latest USB headset plugged in for offloading */ |
186 | + data->active_idx = sdev->card_idx; | 226 | + list_add_tail(&sdev->list, &data->devices); |
227 | + } else { | ||
228 | + list_del(&sdev->list); | ||
187 | + } | 229 | + } |
230 | + mutex_unlock(&data->mutex); | ||
188 | + | 231 | + |
189 | + return 0; | 232 | + return 0; |
190 | +} | 233 | +} |
191 | + | 234 | + |
192 | +static int q6usb_component_probe(struct snd_soc_component *component) | 235 | +static int q6usb_component_probe(struct snd_soc_component *component) |
193 | +{ | 236 | +{ |
194 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); | 237 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); |
195 | + | 238 | + struct snd_soc_usb *usb; |
196 | + data->usb = snd_soc_usb_add_port(component->dev, &data->priv, q6usb_alsa_connection_cb); | 239 | + |
197 | + if (IS_ERR(data->usb)) { | 240 | + usb = snd_soc_usb_allocate_port(component, &data->priv); |
198 | + dev_err(component->dev, "failed to add usb port\n"); | 241 | + if (IS_ERR(usb)) |
199 | + return -ENODEV; | 242 | + return -ENOMEM; |
200 | + } | 243 | + |
201 | + | 244 | + usb->connection_status_cb = q6usb_alsa_connection_cb; |
202 | + data->usb->component = component; | 245 | + |
246 | + snd_soc_usb_add_port(usb); | ||
247 | + data->usb = usb; | ||
203 | + | 248 | + |
204 | + return 0; | 249 | + return 0; |
205 | +} | 250 | +} |
206 | + | 251 | + |
207 | +static void q6usb_component_remove(struct snd_soc_component *component) | 252 | +static void q6usb_component_remove(struct snd_soc_component *component) |
208 | +{ | 253 | +{ |
209 | + snd_soc_usb_remove_port(component->dev); | 254 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); |
255 | + | ||
256 | + snd_soc_usb_remove_port(data->usb); | ||
257 | + snd_soc_usb_free_port(data->usb); | ||
210 | +} | 258 | +} |
211 | + | 259 | + |
212 | +static const struct snd_soc_component_driver q6usb_dai_component = { | 260 | +static const struct snd_soc_component_driver q6usb_dai_component = { |
213 | + .probe = q6usb_component_probe, | 261 | + .probe = q6usb_component_probe, |
214 | + .remove = q6usb_component_remove, | 262 | + .remove = q6usb_component_remove, |
... | ... | ||
231 | + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); | 279 | + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
232 | + if (!data) | 280 | + if (!data) |
233 | + return -ENOMEM; | 281 | + return -ENOMEM; |
234 | + | 282 | + |
235 | + ret = of_property_read_u16(node, "qcom,usb-audio-intr-idx", | 283 | + ret = of_property_read_u16(node, "qcom,usb-audio-intr-idx", |
236 | + &data->priv.intr_num); | 284 | + &data->priv.intr_num); |
237 | + if (ret) { | 285 | + if (ret) { |
238 | + dev_err(&pdev->dev, "failed to read intr idx.\n"); | 286 | + dev_err(&pdev->dev, "failed to read intr idx.\n"); |
239 | + return ret; | 287 | + return ret; |
240 | + } | 288 | + } |
241 | + | 289 | + |
242 | + ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); | 290 | + ret = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); |
243 | + if (ret < 0) | 291 | + if (ret < 0) |
244 | + data->priv.sid = -1; | 292 | + data->priv.sid = -1; |
245 | + else | 293 | + else |
246 | + data->priv.sid = args.args[0] & SID_MASK; | 294 | + data->priv.sid = args.args[0] & Q6_USB_SID_MASK; |
247 | + | 295 | + |
248 | + data->priv.domain = iommu_get_domain_for_dev(&pdev->dev); | 296 | + data->priv.domain = iommu_get_domain_for_dev(&pdev->dev); |
249 | + | 297 | + |
250 | + data->priv.dev = dev; | 298 | + data->priv.dev = dev; |
299 | + INIT_LIST_HEAD(&data->devices); | ||
251 | + dev_set_drvdata(dev, data); | 300 | + dev_set_drvdata(dev, data); |
252 | + | 301 | + |
253 | + return devm_snd_soc_register_component(dev, &q6usb_dai_component, | 302 | + return devm_snd_soc_register_component(dev, &q6usb_dai_component, |
254 | + q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais)); | 303 | + q6usb_be_dais, ARRAY_SIZE(q6usb_be_dais)); |
255 | +} | 304 | +} |
... | ... | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | Add the definition for how many interrupts the XHCI host controller should | ||
2 | allocate. XHCI can potentially support up to 1024 interrupters, which | ||
3 | implementations may want to limit. | ||
4 | 1 | ||
5 | Reviewed-by: Rob Herring <robh@kernel.org> | ||
6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | ||
7 | --- | ||
8 | Documentation/devicetree/bindings/usb/usb-xhci.yaml | 6 ++++++ | ||
9 | 1 file changed, 6 insertions(+) | ||
10 | |||
11 | diff --git a/Documentation/devicetree/bindings/usb/usb-xhci.yaml b/Documentation/devicetree/bindings/usb/usb-xhci.yaml | ||
12 | index XXXXXXX..XXXXXXX 100644 | ||
13 | --- a/Documentation/devicetree/bindings/usb/usb-xhci.yaml | ||
14 | +++ b/Documentation/devicetree/bindings/usb/usb-xhci.yaml | ||
15 | @@ -XXX,XX +XXX,XX @@ properties: | ||
16 | description: Interrupt moderation interval | ||
17 | default: 5000 | ||
18 | |||
19 | + num-hc-interrupters: | ||
20 | + description: Maximum number of interrupters to allocate | ||
21 | + $ref: /schemas/types.yaml#/definitions/uint16 | ||
22 | + minimum: 1 | ||
23 | + maximum: 1024 | ||
24 | + | ||
25 | additionalProperties: true | ||
26 | |||
27 | examples: | diff view generated by jsdifflib |
Deleted patch | |||
---|---|---|---|
1 | Ensure that the number of XHCI secondary interrupters defined for a DWC3 | ||
2 | based implementation is limited to 8. XHCI in general can potentially | ||
3 | support up to 1024 interrupters. | ||
4 | 1 | ||
5 | Reviewed-by: Rob Herring <robh@kernel.org> | ||
6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | ||
7 | --- | ||
8 | Documentation/devicetree/bindings/usb/snps,dwc3.yaml | 4 ++++ | ||
9 | 1 file changed, 4 insertions(+) | ||
10 | |||
11 | diff --git a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml | ||
12 | index XXXXXXX..XXXXXXX 100644 | ||
13 | --- a/Documentation/devicetree/bindings/usb/snps,dwc3.yaml | ||
14 | +++ b/Documentation/devicetree/bindings/usb/snps,dwc3.yaml | ||
15 | @@ -XXX,XX +XXX,XX @@ properties: | ||
16 | items: | ||
17 | enum: [1, 4, 8, 16, 32, 64, 128, 256] | ||
18 | |||
19 | + num-hc-interrupters: | ||
20 | + maximum: 8 | ||
21 | + default: 1 | ||
22 | + | ||
23 | port: | ||
24 | $ref: /schemas/graph.yaml#/properties/port | ||
25 | description: | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
4 | Applications can further identify specific offloading information through | 4 | Applications can further identify specific offloading information through |
5 | other SND kcontrols. | 5 | other SND kcontrols. |
6 | 6 | ||
7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
8 | --- | 8 | --- |
9 | sound/soc/qcom/qdsp6/q6usb.c | 14 ++++++++++++++ | 9 | sound/soc/qcom/Kconfig | 5 +++ |
10 | 1 file changed, 14 insertions(+) | 10 | sound/soc/qcom/Makefile | 2 ++ |
11 | sound/soc/qcom/qdsp6/q6usb.c | 41 ++++++++++++++++++++++ | ||
12 | sound/soc/qcom/sm8250.c | 24 ++++++++++++- | ||
13 | sound/soc/qcom/usb_offload_utils.c | 56 ++++++++++++++++++++++++++++++ | ||
14 | sound/soc/qcom/usb_offload_utils.h | 30 ++++++++++++++++ | ||
15 | 6 files changed, 157 insertions(+), 1 deletion(-) | ||
16 | create mode 100644 sound/soc/qcom/usb_offload_utils.c | ||
17 | create mode 100644 sound/soc/qcom/usb_offload_utils.h | ||
11 | 18 | ||
19 | diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig | ||
20 | index XXXXXXX..XXXXXXX 100644 | ||
21 | --- a/sound/soc/qcom/Kconfig | ||
22 | +++ b/sound/soc/qcom/Kconfig | ||
23 | @@ -XXX,XX +XXX,XX @@ config SND_SOC_QDSP6_PRM | ||
24 | tristate | ||
25 | select SND_SOC_QDSP6_PRM_LPASS_CLOCKS | ||
26 | |||
27 | +config SND_SOC_QCOM_OFFLOAD_UTILS | ||
28 | + tristate | ||
29 | + | ||
30 | config SND_SOC_QDSP6_USB | ||
31 | tristate "SoC ALSA USB offloading backing for QDSP6" | ||
32 | depends on SND_SOC_USB | ||
33 | + select SND_SOC_QCOM_OFFLOAD_UTILS | ||
34 | + | ||
35 | help | ||
36 | Adds support for USB offloading for QDSP6 ASoC | ||
37 | based platform sound cards. This will enable the | ||
38 | diff --git a/sound/soc/qcom/Makefile b/sound/soc/qcom/Makefile | ||
39 | index XXXXXXX..XXXXXXX 100644 | ||
40 | --- a/sound/soc/qcom/Makefile | ||
41 | +++ b/sound/soc/qcom/Makefile | ||
42 | @@ -XXX,XX +XXX,XX @@ snd-soc-sc8280xp-y := sc8280xp.o | ||
43 | snd-soc-qcom-common-y := common.o | ||
44 | snd-soc-qcom-sdw-y := sdw.o | ||
45 | snd-soc-x1e80100-y := x1e80100.o | ||
46 | +snd-soc-qcom-offload-utils-objs := usb_offload_utils.o | ||
47 | |||
48 | obj-$(CONFIG_SND_SOC_STORM) += snd-soc-storm.o | ||
49 | obj-$(CONFIG_SND_SOC_APQ8016_SBC) += snd-soc-apq8016-sbc.o | ||
50 | @@ -XXX,XX +XXX,XX @@ obj-$(CONFIG_SND_SOC_SM8250) += snd-soc-sm8250.o | ||
51 | obj-$(CONFIG_SND_SOC_QCOM_COMMON) += snd-soc-qcom-common.o | ||
52 | obj-$(CONFIG_SND_SOC_QCOM_SDW) += snd-soc-qcom-sdw.o | ||
53 | obj-$(CONFIG_SND_SOC_X1E80100) += snd-soc-x1e80100.o | ||
54 | +obj-$(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) += snd-soc-qcom-offload-utils.o | ||
55 | |||
56 | #DSP lib | ||
57 | obj-$(CONFIG_SND_SOC_QDSP6) += qdsp6/ | ||
12 | diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c | 58 | diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c |
13 | index XXXXXXX..XXXXXXX 100644 | 59 | index XXXXXXX..XXXXXXX 100644 |
14 | --- a/sound/soc/qcom/qdsp6/q6usb.c | 60 | --- a/sound/soc/qcom/qdsp6/q6usb.c |
15 | +++ b/sound/soc/qcom/qdsp6/q6usb.c | 61 | +++ b/sound/soc/qcom/qdsp6/q6usb.c |
16 | @@ -XXX,XX +XXX,XX @@ | 62 | @@ -XXX,XX +XXX,XX @@ |
63 | #include <linux/slab.h> | ||
64 | |||
65 | #include <sound/asound.h> | ||
66 | +#include <sound/jack.h> | ||
67 | #include <sound/pcm.h> | ||
17 | #include <sound/pcm_params.h> | 68 | #include <sound/pcm_params.h> |
18 | #include <sound/asound.h> | ||
19 | #include <sound/q6usboffload.h> | 69 | #include <sound/q6usboffload.h> |
20 | +#include <sound/jack.h> | 70 | @@ -XXX,XX +XXX,XX @@ |
21 | |||
22 | #include "q6dsp-lpass-ports.h" | ||
23 | #include "q6afe.h" | ||
24 | @@ -XXX,XX +XXX,XX @@ struct q6usb_status { | ||
25 | struct q6usb_port_data { | 71 | struct q6usb_port_data { |
26 | struct q6afe_usb_cfg usb_cfg; | 72 | struct q6afe_usb_cfg usb_cfg; |
27 | struct snd_soc_usb *usb; | 73 | struct snd_soc_usb *usb; |
28 | + struct snd_soc_jack hs_jack; | 74 | + struct snd_soc_jack *hs_jack; |
29 | struct q6usb_offload priv; | 75 | struct q6usb_offload priv; |
30 | struct mutex mutex; | 76 | |
31 | unsigned long available_card_slot; | 77 | /* Protects against operations between SOC USB and ASoC */ |
32 | @@ -XXX,XX +XXX,XX @@ static const struct snd_kcontrol_new q6usb_offload_control = { | ||
33 | /* Build a mixer control for a UAC connector control (jack-detect) */ | ||
34 | static void q6usb_connector_control_init(struct snd_soc_component *component) | ||
35 | { | ||
36 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); | ||
37 | int ret; | ||
38 | |||
39 | ret = snd_ctl_add(component->card->snd_card, | ||
40 | @@ -XXX,XX +XXX,XX @@ static void q6usb_connector_control_init(struct snd_soc_component *component) | ||
41 | snd_ctl_new1(&q6usb_offload_dev_ctrl, component)); | ||
42 | if (ret < 0) | ||
43 | return; | ||
44 | + | ||
45 | + ret = snd_soc_card_jack_new(component->card, "USB offload", | ||
46 | + SND_JACK_HEADSET, &data->hs_jack); | ||
47 | + if (ret) | ||
48 | + return; | ||
49 | } | ||
50 | |||
51 | static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, | ||
52 | @@ -XXX,XX +XXX,XX @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, | 78 | @@ -XXX,XX +XXX,XX @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, |
53 | 79 | ||
54 | mutex_lock(&data->mutex); | 80 | mutex_lock(&data->mutex); |
55 | if (connected) { | 81 | if (connected) { |
56 | + if (!data->available_card_slot) | 82 | + if (data->hs_jack) |
57 | + snd_jack_report(data->hs_jack.jack, 1); | 83 | + snd_jack_report(data->hs_jack->jack, SND_JACK_USB); |
58 | + | 84 | + |
59 | /* | 85 | /* Selects the latest USB headset plugged in for offloading */ |
60 | * Update the latest USB headset plugged in, if session is | 86 | list_add_tail(&sdev->list, &data->devices); |
61 | * idle. | 87 | } else { |
62 | @@ -XXX,XX +XXX,XX @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, | 88 | list_del(&sdev->list); |
63 | clear_bit(sdev->card_idx, &data->available_card_slot); | 89 | + |
64 | data->status[sdev->card_idx].num_pcm = 0; | 90 | + if (data->hs_jack) |
65 | data->status[sdev->card_idx].chip_index = 0; | 91 | + snd_jack_report(data->hs_jack->jack, 0); |
66 | + | ||
67 | + if (!data->available_card_slot) | ||
68 | + snd_jack_report(data->hs_jack.jack, 0); | ||
69 | } | 92 | } |
70 | mutex_unlock(&data->mutex); | 93 | mutex_unlock(&data->mutex); |
94 | |||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | +static void q6usb_component_disable_jack(struct q6usb_port_data *data) | ||
99 | +{ | ||
100 | + /* Offload jack has already been disabled */ | ||
101 | + if (!data->hs_jack) | ||
102 | + return; | ||
103 | + | ||
104 | + snd_jack_report(data->hs_jack->jack, 0); | ||
105 | + data->hs_jack = NULL; | ||
106 | +} | ||
107 | + | ||
108 | +static void q6usb_component_enable_jack(struct q6usb_port_data *data, | ||
109 | + struct snd_soc_jack *jack) | ||
110 | +{ | ||
111 | + snd_jack_report(jack->jack, !list_empty(&data->devices) ? SND_JACK_USB : 0); | ||
112 | + data->hs_jack = jack; | ||
113 | +} | ||
114 | + | ||
115 | +static int q6usb_component_set_jack(struct snd_soc_component *component, | ||
116 | + struct snd_soc_jack *jack, void *priv) | ||
117 | +{ | ||
118 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); | ||
119 | + | ||
120 | + mutex_lock(&data->mutex); | ||
121 | + if (jack) | ||
122 | + q6usb_component_enable_jack(data, jack); | ||
123 | + else | ||
124 | + q6usb_component_disable_jack(data); | ||
125 | + mutex_unlock(&data->mutex); | ||
126 | + | ||
127 | + return 0; | ||
128 | +} | ||
129 | + | ||
130 | static int q6usb_component_probe(struct snd_soc_component *component) | ||
131 | { | ||
132 | struct q6usb_port_data *data = dev_get_drvdata(component->dev); | ||
133 | @@ -XXX,XX +XXX,XX @@ static void q6usb_component_remove(struct snd_soc_component *component) | ||
134 | |||
135 | static const struct snd_soc_component_driver q6usb_dai_component = { | ||
136 | .probe = q6usb_component_probe, | ||
137 | + .set_jack = q6usb_component_set_jack, | ||
138 | .remove = q6usb_component_remove, | ||
139 | .name = "q6usb-dai-component", | ||
140 | .dapm_widgets = q6usb_dai_widgets, | ||
141 | diff --git a/sound/soc/qcom/sm8250.c b/sound/soc/qcom/sm8250.c | ||
142 | index XXXXXXX..XXXXXXX 100644 | ||
143 | --- a/sound/soc/qcom/sm8250.c | ||
144 | +++ b/sound/soc/qcom/sm8250.c | ||
145 | @@ -XXX,XX +XXX,XX @@ | ||
146 | #include <linux/input-event-codes.h> | ||
147 | #include "qdsp6/q6afe.h" | ||
148 | #include "common.h" | ||
149 | +#include "usb_offload_utils.h" | ||
150 | #include "sdw.h" | ||
151 | |||
152 | #define DRIVER_NAME "sm8250" | ||
153 | @@ -XXX,XX +XXX,XX @@ struct sm8250_snd_data { | ||
154 | struct snd_soc_card *card; | ||
155 | struct sdw_stream_runtime *sruntime[AFE_PORT_MAX]; | ||
156 | struct snd_soc_jack jack; | ||
157 | + struct snd_soc_jack usb_offload_jack; | ||
158 | + bool usb_offload_jack_setup; | ||
159 | bool jack_setup; | ||
160 | }; | ||
161 | |||
162 | static int sm8250_snd_init(struct snd_soc_pcm_runtime *rtd) | ||
163 | { | ||
164 | struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); | ||
165 | + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | ||
166 | + int ret; | ||
167 | + | ||
168 | + if (cpu_dai->id == USB_RX) | ||
169 | + ret = qcom_snd_usb_offload_jack_setup(rtd, &data->usb_offload_jack, | ||
170 | + &data->usb_offload_jack_setup); | ||
171 | + else | ||
172 | + ret = qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); | ||
173 | + return ret; | ||
174 | +} | ||
175 | + | ||
176 | +static void sm8250_snd_exit(struct snd_soc_pcm_runtime *rtd) | ||
177 | +{ | ||
178 | + struct sm8250_snd_data *data = snd_soc_card_get_drvdata(rtd->card); | ||
179 | + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | ||
180 | + | ||
181 | + if (cpu_dai->id == USB_RX) | ||
182 | + qcom_snd_usb_offload_jack_remove(rtd, | ||
183 | + &data->usb_offload_jack_setup); | ||
184 | |||
185 | - return qcom_snd_wcd_jack_setup(rtd, &data->jack, &data->jack_setup); | ||
186 | } | ||
187 | |||
188 | static int sm8250_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, | ||
189 | @@ -XXX,XX +XXX,XX @@ static void sm8250_add_be_ops(struct snd_soc_card *card) | ||
190 | for_each_card_prelinks(card, i, link) { | ||
191 | if (link->no_pcm == 1) { | ||
192 | link->init = sm8250_snd_init; | ||
193 | + link->exit = sm8250_snd_exit; | ||
194 | link->be_hw_params_fixup = sm8250_be_hw_params_fixup; | ||
195 | link->ops = &sm8250_be_ops; | ||
196 | } | ||
197 | diff --git a/sound/soc/qcom/usb_offload_utils.c b/sound/soc/qcom/usb_offload_utils.c | ||
198 | new file mode 100644 | ||
199 | index XXXXXXX..XXXXXXX | ||
200 | --- /dev/null | ||
201 | +++ b/sound/soc/qcom/usb_offload_utils.c | ||
202 | @@ -XXX,XX +XXX,XX @@ | ||
203 | +// SPDX-License-Identifier: GPL-2.0 | ||
204 | +/* | ||
205 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. | ||
206 | + */ | ||
207 | +#include <dt-bindings/sound/qcom,q6afe.h> | ||
208 | +#include <linux/module.h> | ||
209 | +#include <sound/jack.h> | ||
210 | +#include <sound/soc-usb.h> | ||
211 | + | ||
212 | +#include "usb_offload_utils.h" | ||
213 | + | ||
214 | +int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd, | ||
215 | + struct snd_soc_jack *jack, bool *jack_setup) | ||
216 | +{ | ||
217 | + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | ||
218 | + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); | ||
219 | + int ret = 0; | ||
220 | + | ||
221 | + if (cpu_dai->id != USB_RX) | ||
222 | + return -EINVAL; | ||
223 | + | ||
224 | + if (!*jack_setup) { | ||
225 | + ret = snd_soc_usb_setup_offload_jack(codec_dai->component, jack); | ||
226 | + if (ret) | ||
227 | + return ret; | ||
228 | + } | ||
229 | + | ||
230 | + *jack_setup = true; | ||
231 | + | ||
232 | + return 0; | ||
233 | +} | ||
234 | +EXPORT_SYMBOL_GPL(qcom_snd_usb_offload_jack_setup); | ||
235 | + | ||
236 | +int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd, | ||
237 | + bool *jack_setup) | ||
238 | +{ | ||
239 | + struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); | ||
240 | + struct snd_soc_dai *codec_dai = snd_soc_rtd_to_codec(rtd, 0); | ||
241 | + int ret = 0; | ||
242 | + | ||
243 | + if (cpu_dai->id != USB_RX) | ||
244 | + return -EINVAL; | ||
245 | + | ||
246 | + if (*jack_setup) { | ||
247 | + ret = snd_soc_component_set_jack(codec_dai->component, NULL, NULL); | ||
248 | + if (ret) | ||
249 | + return ret; | ||
250 | + } | ||
251 | + | ||
252 | + *jack_setup = false; | ||
253 | + | ||
254 | + return 0; | ||
255 | +} | ||
256 | +EXPORT_SYMBOL_GPL(qcom_snd_usb_offload_jack_remove); | ||
257 | +MODULE_DESCRIPTION("ASoC Q6 USB offload controls"); | ||
258 | +MODULE_LICENSE("GPL"); | ||
259 | diff --git a/sound/soc/qcom/usb_offload_utils.h b/sound/soc/qcom/usb_offload_utils.h | ||
260 | new file mode 100644 | ||
261 | index XXXXXXX..XXXXXXX | ||
262 | --- /dev/null | ||
263 | +++ b/sound/soc/qcom/usb_offload_utils.h | ||
264 | @@ -XXX,XX +XXX,XX @@ | ||
265 | +/* SPDX-License-Identifier: GPL-2.0 | ||
266 | + * | ||
267 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. | ||
268 | + */ | ||
269 | +#ifndef __QCOM_SND_USB_OFFLOAD_UTILS_H__ | ||
270 | +#define __QCOM_SND_USB_OFFLOAD_UTILS_H__ | ||
271 | + | ||
272 | +#include <sound/soc.h> | ||
273 | + | ||
274 | +#if IS_ENABLED(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) | ||
275 | +int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd, | ||
276 | + struct snd_soc_jack *jack, bool *jack_setup); | ||
277 | + | ||
278 | +int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd, | ||
279 | + bool *jack_setup); | ||
280 | +#else | ||
281 | +static inline int qcom_snd_usb_offload_jack_setup(struct snd_soc_pcm_runtime *rtd, | ||
282 | + struct snd_soc_jack *jack, | ||
283 | + bool *jack_setup) | ||
284 | +{ | ||
285 | + return -ENODEV; | ||
286 | +} | ||
287 | + | ||
288 | +static inline int qcom_snd_usb_offload_jack_remove(struct snd_soc_pcm_runtime *rtd, | ||
289 | + bool *jack_setup) | ||
290 | +{ | ||
291 | + return -ENODEV; | ||
292 | +} | ||
293 | +#endif /* IS_ENABLED(CONFIG_SND_SOC_QCOM_OFFLOAD_UTILS) */ | ||
294 | +#endif /* __QCOM_SND_USB_OFFLOAD_UTILS_H__ */ | diff view generated by jsdifflib |
1 | Add a kcontrol to the platform sound card to fetch the current offload | 1 | The USB SND path may need to know how the USB offload path is routed, so |
---|---|---|---|
2 | status. This can allow for userspace to ensure/check which USB SND | 2 | that applications can open the proper sound card and PCM device. The |
3 | resources are actually busy versus having to attempt opening the USB SND | 3 | implementation for the QC ASoC design has a "USB Mixer" kcontrol for each |
4 | devices, which will result in an error if offloading is active. | 4 | possible FE (Q6ASM) DAI, which can be utilized to know which front end link |
5 | is enabled. | ||
6 | |||
7 | When an application/userspace queries for the mapped offload devices, the | ||
8 | logic will lookup the USB mixer status though the following path: | ||
9 | |||
10 | MultiMedia* <-> MM_DL* <-> USB Mixer* | ||
11 | |||
12 | The "USB Mixer" is a DAPM widget, and the q6routing entity will set the | ||
13 | DAPM connect status accordingly if the USB mixer is enabled. If enabled, | ||
14 | the Q6USB backend link can fetch the PCM device number from the FE DAI | ||
15 | link (Multimedia*). With respects to the card number, that is | ||
16 | straightforward, as the ASoC components have direct references to the ASoC | ||
17 | platform sound card. | ||
18 | |||
19 | An example output can be shown below: | ||
20 | |||
21 | Number of controls: 9 | ||
22 | name value | ||
23 | Capture Channel Map 0, 0 (range 0->36) | ||
24 | Playback Channel Map 0, 0 (range 0->36) | ||
25 | Headset Capture Switch On | ||
26 | Headset Capture Volume 1 (range 0->4) | ||
27 | Sidetone Playback Switch On | ||
28 | Sidetone Playback Volume 4096 (range 0->8192) | ||
29 | Headset Playback Switch On | ||
30 | Headset Playback Volume 20, 20 (range 0->24) | ||
31 | USB Offload Playback Route PCM#0 0, 1 (range -1->255) | ||
32 | |||
33 | The "USB Offload Playback Route PCM#*" kcontrol will signify the | ||
34 | corresponding card and pcm device it is offload to. (card#0 pcm - device#1) | ||
35 | If the USB SND device supports multiple audio interfaces, then it will | ||
36 | contain several PCM streams, hence in those situations, it is expected | ||
37 | that there will be multiple playback route kcontrols created. | ||
5 | 38 | ||
6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 39 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
7 | --- | 40 | --- |
8 | sound/soc/qcom/qdsp6/q6usb.c | 104 ++++++++++++++++++++++++++++++++++- | 41 | sound/soc/qcom/qdsp6/q6usb.c | 98 ++++++++++++++++++++++++++++++++++++ |
9 | 1 file changed, 101 insertions(+), 3 deletions(-) | 42 | 1 file changed, 98 insertions(+) |
10 | 43 | ||
11 | diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c | 44 | diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c |
12 | index XXXXXXX..XXXXXXX 100644 | 45 | index XXXXXXX..XXXXXXX 100644 |
13 | --- a/sound/soc/qcom/qdsp6/q6usb.c | 46 | --- a/sound/soc/qcom/qdsp6/q6usb.c |
14 | +++ b/sound/soc/qcom/qdsp6/q6usb.c | 47 | +++ b/sound/soc/qcom/qdsp6/q6usb.c |
15 | @@ -XXX,XX +XXX,XX @@ struct q6usb_status { | 48 | @@ -XXX,XX +XXX,XX @@ static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *compone |
16 | unsigned int num_pcm; | 49 | return ret; |
17 | unsigned int chip_index; | 50 | } |
18 | unsigned int pcm_index; | 51 | |
19 | + bool prepared; | 52 | +static int q6usb_get_pcm_id_from_widget(struct snd_soc_dapm_widget *w) |
20 | + bool running; | ||
21 | }; | ||
22 | |||
23 | struct q6usb_port_data { | ||
24 | @@ -XXX,XX +XXX,XX @@ static const struct snd_soc_dapm_route q6usb_dapm_routes[] = { | ||
25 | {"USB Playback", NULL, "USB_RX_BE"}, | ||
26 | }; | ||
27 | |||
28 | +static int q6usb_find_running(struct q6usb_port_data *data) | ||
29 | +{ | 53 | +{ |
30 | + int i; | 54 | + struct snd_soc_pcm_runtime *rtd; |
55 | + struct snd_soc_dai *dai; | ||
31 | + | 56 | + |
32 | + for (i = 0; i < SNDRV_CARDS; i++) { | 57 | + for_each_card_rtds(w->dapm->card, rtd) { |
33 | + if (data->status[i].running) | 58 | + dai = snd_soc_rtd_to_cpu(rtd, 0); |
34 | + return i; | 59 | + /* |
60 | + * Only look for playback widget. RTD number carries the assigned | ||
61 | + * PCM index. | ||
62 | + */ | ||
63 | + if (dai->stream[0].widget == w) | ||
64 | + return rtd->id; | ||
35 | + } | 65 | + } |
36 | + return -ENODEV; | 66 | + |
67 | + return -1; | ||
37 | +} | 68 | +} |
38 | + | 69 | + |
39 | static int q6usb_hw_params(struct snd_pcm_substream *substream, | 70 | +static int q6usb_usb_mixer_enabled(struct snd_soc_dapm_widget *w) |
40 | struct snd_pcm_hw_params *params, | ||
41 | struct snd_soc_dai *dai) | ||
42 | @@ -XXX,XX +XXX,XX @@ static int q6usb_hw_params(struct snd_pcm_substream *substream, | ||
43 | goto out; | ||
44 | |||
45 | data->status[data->sel_card_idx].pcm_index = data->sel_pcm_idx; | ||
46 | + data->status[data->sel_card_idx].prepared = true; | ||
47 | out: | ||
48 | mutex_unlock(&data->mutex); | ||
49 | |||
50 | return ret; | ||
51 | } | ||
52 | |||
53 | +static int q6usb_prepare(struct snd_pcm_substream *substream, | ||
54 | + struct snd_soc_dai *dai) | ||
55 | +{ | 71 | +{ |
56 | + struct q6usb_port_data *data = dev_get_drvdata(dai->dev); | 72 | + struct snd_soc_dapm_path *p; |
57 | + | 73 | + |
58 | + mutex_lock(&data->mutex); | 74 | + /* Checks to ensure USB path is enabled/connected */ |
59 | + data->status[data->sel_card_idx].running = true; | 75 | + snd_soc_dapm_widget_for_each_sink_path(w, p) |
60 | + mutex_unlock(&data->mutex); | 76 | + if (!strcmp(p->sink->name, "USB Mixer") && p->connect) |
77 | + return 1; | ||
61 | + | 78 | + |
62 | + return 0; | 79 | + return 0; |
63 | +} | 80 | +} |
64 | + | 81 | + |
65 | +static void q6usb_shutdown(struct snd_pcm_substream *substream, | 82 | +static int q6usb_get_pcm_id(struct snd_soc_component *component) |
66 | + struct snd_soc_dai *dai) | ||
67 | +{ | 83 | +{ |
68 | + struct q6usb_port_data *data = dev_get_drvdata(dai->dev); | 84 | + struct snd_soc_dapm_widget *w; |
85 | + struct snd_soc_dapm_path *p; | ||
86 | + int pidx; | ||
87 | + | ||
88 | + /* | ||
89 | + * Traverse widgets to find corresponding FE widget. The DAI links are | ||
90 | + * built like the following: | ||
91 | + * MultiMedia* <-> MM_DL* <-> USB Mixer* | ||
92 | + */ | ||
93 | + for_each_card_widgets(component->card, w) { | ||
94 | + if (!strncmp(w->name, "MultiMedia", 10)) { | ||
95 | + /* | ||
96 | + * Look up all paths associated with the FE widget to see if | ||
97 | + * the USB BE is enabled. The sink widget is responsible to | ||
98 | + * link with the USB mixers. | ||
99 | + */ | ||
100 | + snd_soc_dapm_widget_for_each_sink_path(w, p) { | ||
101 | + if (q6usb_usb_mixer_enabled(p->sink)) { | ||
102 | + pidx = q6usb_get_pcm_id_from_widget(w); | ||
103 | + return pidx; | ||
104 | + } | ||
105 | + } | ||
106 | + } | ||
107 | + } | ||
108 | + | ||
109 | + return -1; | ||
110 | +} | ||
111 | + | ||
112 | +static int q6usb_update_offload_route(struct snd_soc_component *component, int card, | ||
113 | + int pcm, int direction, enum snd_soc_usb_kctl path, | ||
114 | + long *route) | ||
115 | +{ | ||
116 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); | ||
117 | + struct snd_soc_usb_device *sdev; | ||
118 | + int ret = 0; | ||
119 | + int idx = -1; | ||
69 | + | 120 | + |
70 | + mutex_lock(&data->mutex); | 121 | + mutex_lock(&data->mutex); |
71 | + data->status[data->sel_card_idx].running = false; | 122 | + |
72 | + data->status[data->sel_card_idx].prepared = false; | 123 | + if (list_empty(&data->devices) || |
124 | + direction == SNDRV_PCM_STREAM_CAPTURE) { | ||
125 | + ret = -ENODEV; | ||
126 | + goto out; | ||
127 | + } | ||
128 | + | ||
129 | + sdev = list_last_entry(&data->devices, struct snd_soc_usb_device, list); | ||
130 | + | ||
131 | + /* | ||
132 | + * Will always look for last PCM device discovered/probed as the | ||
133 | + * active offload index. | ||
134 | + */ | ||
135 | + if (card == sdev->card_idx && | ||
136 | + pcm == sdev->ppcm_idx[sdev->num_playback - 1]) { | ||
137 | + idx = path == SND_SOC_USB_KCTL_CARD_ROUTE ? | ||
138 | + component->card->snd_card->number : | ||
139 | + q6usb_get_pcm_id(component); | ||
140 | + } | ||
141 | + | ||
142 | +out: | ||
143 | + route[0] = idx; | ||
73 | + mutex_unlock(&data->mutex); | 144 | + mutex_unlock(&data->mutex); |
145 | + | ||
146 | + return ret; | ||
74 | +} | 147 | +} |
75 | + | 148 | + |
76 | static const struct snd_soc_dai_ops q6usb_ops = { | 149 | static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, |
77 | .hw_params = q6usb_hw_params, | 150 | struct snd_soc_usb_device *sdev, bool connected) |
78 | + .prepare = q6usb_prepare, | ||
79 | + .shutdown = q6usb_shutdown, | ||
80 | }; | ||
81 | |||
82 | static struct snd_soc_dai_driver q6usb_be_dais[] = { | ||
83 | @@ -XXX,XX +XXX,XX @@ static int q6usb_put_offload_dev(struct snd_kcontrol *kcontrol, | ||
84 | int pcmidx; | ||
85 | int cardidx; | ||
86 | |||
87 | + mutex_lock(&data->mutex); | ||
88 | + | ||
89 | + /* Don't allow changes to the offloading devices if session is busy */ | ||
90 | + if (data->sel_card_idx >= 0 && data->status[data->sel_card_idx].prepared) | ||
91 | + goto out; | ||
92 | + | ||
93 | cardidx = ucontrol->value.integer.value[0]; | ||
94 | pcmidx = ucontrol->value.integer.value[1]; | ||
95 | |||
96 | - mutex_lock(&data->mutex); | ||
97 | if ((cardidx >= 0 && test_bit(cardidx, &data->available_card_slot))) { | ||
98 | data->sel_card_idx = cardidx; | ||
99 | changed = 1; | ||
100 | @@ -XXX,XX +XXX,XX @@ static int q6usb_put_offload_dev(struct snd_kcontrol *kcontrol, | ||
101 | data->idx_valid = true; | ||
102 | changed = 1; | ||
103 | } | ||
104 | + | ||
105 | +out: | ||
106 | mutex_unlock(&data->mutex); | ||
107 | |||
108 | return changed; | ||
109 | @@ -XXX,XX +XXX,XX @@ static const struct snd_kcontrol_new q6usb_offload_dev_ctrl = { | ||
110 | .put = q6usb_put_offload_dev, | ||
111 | }; | ||
112 | |||
113 | +static int q6usb_mixer_get_offload_status(struct snd_kcontrol *kcontrol, | ||
114 | + struct snd_ctl_elem_value *ucontrol) | ||
115 | +{ | ||
116 | + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); | ||
117 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); | ||
118 | + int running; | ||
119 | + int card_idx; | ||
120 | + int pcm_idx; | ||
121 | + | ||
122 | + running = q6usb_find_running(data); | ||
123 | + if (running < 0) { | ||
124 | + card_idx = -1; | ||
125 | + pcm_idx = -1; | ||
126 | + } else { | ||
127 | + card_idx = running; | ||
128 | + pcm_idx = data->status[running].pcm_index; | ||
129 | + } | ||
130 | + | ||
131 | + ucontrol->value.integer.value[0] = card_idx; | ||
132 | + ucontrol->value.integer.value[1] = pcm_idx; | ||
133 | + return 0; | ||
134 | +} | ||
135 | + | ||
136 | +static int q6usb_offload_ctl_info(struct snd_kcontrol *kcontrol, | ||
137 | + struct snd_ctl_elem_info *uinfo) | ||
138 | +{ | ||
139 | + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | ||
140 | + uinfo->count = 2; | ||
141 | + uinfo->value.integer.min = 0; | ||
142 | + uinfo->value.integer.max = SNDRV_CARDS; | ||
143 | + | ||
144 | + return 0; | ||
145 | +} | ||
146 | + | ||
147 | +static const struct snd_kcontrol_new q6usb_offload_control = { | ||
148 | + .iface = SNDRV_CTL_ELEM_IFACE_CARD, | ||
149 | + .access = SNDRV_CTL_ELEM_ACCESS_READ, | ||
150 | + .name = "Q6USB offload status", | ||
151 | + .info = q6usb_offload_ctl_info, | ||
152 | + .get = q6usb_mixer_get_offload_status, | ||
153 | + .put = NULL, | ||
154 | +}; | ||
155 | + | ||
156 | /* Build a mixer control for a UAC connector control (jack-detect) */ | ||
157 | static void q6usb_connector_control_init(struct snd_soc_component *component) | ||
158 | { | 151 | { |
159 | int ret; | 152 | @@ -XXX,XX +XXX,XX @@ static int q6usb_component_probe(struct snd_soc_component *component) |
160 | 153 | return -ENOMEM; | |
161 | + ret = snd_ctl_add(component->card->snd_card, | 154 | |
162 | + snd_ctl_new1(&q6usb_offload_control, component)); | 155 | usb->connection_status_cb = q6usb_alsa_connection_cb; |
163 | + if (ret < 0) | 156 | + usb->update_offload_route_info = q6usb_update_offload_route; |
164 | + return; | 157 | |
165 | + | 158 | snd_soc_usb_add_port(usb); |
166 | ret = snd_ctl_add(component->card->snd_card, | 159 | data->usb = usb; |
167 | snd_ctl_new1(&q6usb_offload_dev_ctrl, component)); | ||
168 | if (ret < 0) | ||
169 | @@ -XXX,XX +XXX,XX @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, | ||
170 | |||
171 | mutex_lock(&data->mutex); | ||
172 | if (connected) { | ||
173 | - /* We only track the latest USB headset plugged in */ | ||
174 | - if (!data->idx_valid || data->sel_card_idx < 0) | ||
175 | + /* | ||
176 | + * Update the latest USB headset plugged in, if session is | ||
177 | + * idle. | ||
178 | + */ | ||
179 | + if ((!data->idx_valid || data->sel_card_idx < 0) && | ||
180 | + !data->status[data->sel_card_idx].prepared) | ||
181 | data->sel_card_idx = sdev->card_idx; | ||
182 | |||
183 | set_bit(sdev->card_idx, &data->available_card_slot); | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
3 | definitions, so the QMI interface driver is able to route the QMI packet | 3 | definitions, so the QMI interface driver is able to route the QMI packet |
4 | received to the USB audio offload driver. | 4 | received to the USB audio offload driver. |
5 | 5 | ||
6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
7 | --- | 7 | --- |
8 | sound/usb/qcom/usb_audio_qmi_v01.c | 892 +++++++++++++++++++++++++++++ | 8 | sound/usb/qcom/usb_audio_qmi_v01.c | 863 +++++++++++++++++++++++++++++ |
9 | sound/usb/qcom/usb_audio_qmi_v01.h | 162 ++++++ | 9 | sound/usb/qcom/usb_audio_qmi_v01.h | 164 ++++++ |
10 | 2 files changed, 1054 insertions(+) | 10 | 2 files changed, 1027 insertions(+) |
11 | create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.c | 11 | create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.c |
12 | create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.h | 12 | create mode 100644 sound/usb/qcom/usb_audio_qmi_v01.h |
13 | 13 | ||
14 | diff --git a/sound/usb/qcom/usb_audio_qmi_v01.c b/sound/usb/qcom/usb_audio_qmi_v01.c | 14 | diff --git a/sound/usb/qcom/usb_audio_qmi_v01.c b/sound/usb/qcom/usb_audio_qmi_v01.c |
15 | new file mode 100644 | 15 | new file mode 100644 |
16 | index XXXXXXX..XXXXXXX | 16 | index XXXXXXX..XXXXXXX |
17 | --- /dev/null | 17 | --- /dev/null |
18 | +++ b/sound/usb/qcom/usb_audio_qmi_v01.c | 18 | +++ b/sound/usb/qcom/usb_audio_qmi_v01.c |
19 | @@ -XXX,XX +XXX,XX @@ | 19 | @@ -XXX,XX +XXX,XX @@ |
20 | +// SPDX-License-Identifier: GPL-2.0 | 20 | +// SPDX-License-Identifier: GPL-2.0 |
21 | +/* | 21 | +/* |
22 | + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. | 22 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. |
23 | + */ | 23 | + */ |
24 | + | 24 | + |
25 | +#include <linux/soc/qcom/qmi.h> | 25 | +#include <linux/soc/qcom/qmi.h> |
26 | + | 26 | + |
27 | +#include "usb_audio_qmi_v01.h" | 27 | +#include "usb_audio_qmi_v01.h" |
28 | + | 28 | + |
29 | +static struct qmi_elem_info mem_info_v01_ei[] = { | 29 | +static const struct qmi_elem_info mem_info_v01_ei[] = { |
30 | + { | 30 | + { |
31 | + .data_type = QMI_UNSIGNED_8_BYTE, | 31 | + .data_type = QMI_UNSIGNED_8_BYTE, |
32 | + .elem_len = 1, | 32 | + .elem_len = 1, |
33 | + .elem_size = sizeof(u64), | 33 | + .elem_size = sizeof(u64), |
34 | + .array_type = NO_ARRAY, | 34 | + .array_type = NO_ARRAY, |
... | ... | ||
56 | + .array_type = NO_ARRAY, | 56 | + .array_type = NO_ARRAY, |
57 | + .tlv_type = QMI_COMMON_TLV_TYPE, | 57 | + .tlv_type = QMI_COMMON_TLV_TYPE, |
58 | + }, | 58 | + }, |
59 | +}; | 59 | +}; |
60 | + | 60 | + |
61 | +static struct qmi_elem_info apps_mem_info_v01_ei[] = { | 61 | +static const struct qmi_elem_info apps_mem_info_v01_ei[] = { |
62 | + { | 62 | + { |
63 | + .data_type = QMI_STRUCT, | 63 | + .data_type = QMI_STRUCT, |
64 | + .elem_len = 1, | 64 | + .elem_len = 1, |
65 | + .elem_size = sizeof(struct mem_info_v01), | 65 | + .elem_size = sizeof(struct mem_info_v01), |
66 | + .array_type = NO_ARRAY, | 66 | + .array_type = NO_ARRAY, |
... | ... | ||
109 | + .array_type = NO_ARRAY, | 109 | + .array_type = NO_ARRAY, |
110 | + .tlv_type = QMI_COMMON_TLV_TYPE, | 110 | + .tlv_type = QMI_COMMON_TLV_TYPE, |
111 | + }, | 111 | + }, |
112 | +}; | 112 | +}; |
113 | + | 113 | + |
114 | +static struct qmi_elem_info usb_endpoint_descriptor_v01_ei[] = { | 114 | +static const struct qmi_elem_info usb_endpoint_descriptor_v01_ei[] = { |
115 | + { | 115 | + { |
116 | + .data_type = QMI_UNSIGNED_1_BYTE, | 116 | + .data_type = QMI_UNSIGNED_1_BYTE, |
117 | + .elem_len = 1, | 117 | + .elem_len = 1, |
118 | + .elem_size = sizeof(u8), | 118 | + .elem_size = sizeof(u8), |
119 | + .array_type = NO_ARRAY, | 119 | + .array_type = NO_ARRAY, |
... | ... | ||
189 | + .array_type = NO_ARRAY, | 189 | + .array_type = NO_ARRAY, |
190 | + .tlv_type = QMI_COMMON_TLV_TYPE, | 190 | + .tlv_type = QMI_COMMON_TLV_TYPE, |
191 | + }, | 191 | + }, |
192 | +}; | 192 | +}; |
193 | + | 193 | + |
194 | +static struct qmi_elem_info usb_interface_descriptor_v01_ei[] = { | 194 | +static const struct qmi_elem_info usb_interface_descriptor_v01_ei[] = { |
195 | + { | 195 | + { |
196 | + .data_type = QMI_UNSIGNED_1_BYTE, | 196 | + .data_type = QMI_UNSIGNED_1_BYTE, |
197 | + .elem_len = 1, | 197 | + .elem_len = 1, |
198 | + .elem_size = sizeof(u8), | 198 | + .elem_size = sizeof(u8), |
199 | + .array_type = NO_ARRAY, | 199 | + .array_type = NO_ARRAY, |
... | ... | ||
278 | + .array_type = NO_ARRAY, | 278 | + .array_type = NO_ARRAY, |
279 | + .tlv_type = QMI_COMMON_TLV_TYPE, | 279 | + .tlv_type = QMI_COMMON_TLV_TYPE, |
280 | + }, | 280 | + }, |
281 | +}; | 281 | +}; |
282 | + | 282 | + |
283 | +struct qmi_elem_info qmi_uaudio_stream_req_msg_v01_ei[] = { | 283 | +const struct qmi_elem_info qmi_uaudio_stream_req_msg_v01_ei[] = { |
284 | + { | 284 | + { |
285 | + .data_type = QMI_UNSIGNED_1_BYTE, | 285 | + .data_type = QMI_UNSIGNED_1_BYTE, |
286 | + .elem_len = 1, | 286 | + .elem_len = 1, |
287 | + .elem_size = sizeof(u8), | 287 | + .elem_size = sizeof(u8), |
288 | + .array_type = NO_ARRAY, | 288 | + .array_type = NO_ARRAY, |
... | ... | ||
394 | + .array_type = NO_ARRAY, | 394 | + .array_type = NO_ARRAY, |
395 | + .tlv_type = QMI_COMMON_TLV_TYPE, | 395 | + .tlv_type = QMI_COMMON_TLV_TYPE, |
396 | + }, | 396 | + }, |
397 | +}; | 397 | +}; |
398 | + | 398 | + |
399 | +struct qmi_elem_info qmi_uaudio_stream_resp_msg_v01_ei[] = { | 399 | +const struct qmi_elem_info qmi_uaudio_stream_resp_msg_v01_ei[] = { |
400 | + { | 400 | + { |
401 | + .data_type = QMI_STRUCT, | 401 | + .data_type = QMI_STRUCT, |
402 | + .elem_len = 1, | 402 | + .elem_len = 1, |
403 | + .elem_size = sizeof(struct qmi_response_type_v01), | 403 | + .elem_size = sizeof(struct qmi_response_type_v01), |
404 | + .array_type = NO_ARRAY, | 404 | + .array_type = NO_ARRAY, |
405 | + .tlv_type = 0x02, | 405 | + .tlv_type = 0x02, |
406 | + .offset = offsetof( | 406 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
407 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
408 | + resp), | 407 | + resp), |
409 | + .ei_array = qmi_response_type_v01_ei, | 408 | + .ei_array = qmi_response_type_v01_ei, |
410 | + }, | 409 | + }, |
411 | + { | 410 | + { |
412 | + .data_type = QMI_OPT_FLAG, | 411 | + .data_type = QMI_OPT_FLAG, |
413 | + .elem_len = 1, | 412 | + .elem_len = 1, |
414 | + .elem_size = sizeof(u8), | 413 | + .elem_size = sizeof(u8), |
415 | + .array_type = NO_ARRAY, | 414 | + .array_type = NO_ARRAY, |
416 | + .tlv_type = 0x10, | 415 | + .tlv_type = 0x10, |
417 | + .offset = offsetof( | 416 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
418 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
419 | + status_valid), | 417 | + status_valid), |
420 | + }, | 418 | + }, |
421 | + { | 419 | + { |
422 | + .data_type = QMI_SIGNED_4_BYTE_ENUM, | 420 | + .data_type = QMI_SIGNED_4_BYTE_ENUM, |
423 | + .elem_len = 1, | 421 | + .elem_len = 1, |
424 | + .elem_size = sizeof(enum usb_qmi_audio_stream_status_enum_v01), | 422 | + .elem_size = sizeof(enum usb_qmi_audio_stream_status_enum_v01), |
425 | + .array_type = NO_ARRAY, | 423 | + .array_type = NO_ARRAY, |
426 | + .tlv_type = 0x10, | 424 | + .tlv_type = 0x10, |
427 | + .offset = offsetof( | 425 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
428 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
429 | + status), | 426 | + status), |
430 | + }, | 427 | + }, |
431 | + { | 428 | + { |
432 | + .data_type = QMI_OPT_FLAG, | 429 | + .data_type = QMI_OPT_FLAG, |
433 | + .elem_len = 1, | 430 | + .elem_len = 1, |
434 | + .elem_size = sizeof(u8), | 431 | + .elem_size = sizeof(u8), |
435 | + .array_type = NO_ARRAY, | 432 | + .array_type = NO_ARRAY, |
436 | + .tlv_type = 0x11, | 433 | + .tlv_type = 0x11, |
437 | + .offset = offsetof( | 434 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
438 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
439 | + internal_status_valid), | 435 | + internal_status_valid), |
440 | + }, | 436 | + }, |
441 | + { | 437 | + { |
442 | + .data_type = QMI_UNSIGNED_4_BYTE, | 438 | + .data_type = QMI_UNSIGNED_4_BYTE, |
443 | + .elem_len = 1, | 439 | + .elem_len = 1, |
444 | + .elem_size = sizeof(u32), | 440 | + .elem_size = sizeof(u32), |
445 | + .array_type = NO_ARRAY, | 441 | + .array_type = NO_ARRAY, |
446 | + .tlv_type = 0x11, | 442 | + .tlv_type = 0x11, |
447 | + .offset = offsetof( | 443 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
448 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
449 | + internal_status), | 444 | + internal_status), |
450 | + }, | 445 | + }, |
451 | + { | 446 | + { |
452 | + .data_type = QMI_OPT_FLAG, | 447 | + .data_type = QMI_OPT_FLAG, |
453 | + .elem_len = 1, | 448 | + .elem_len = 1, |
454 | + .elem_size = sizeof(u8), | 449 | + .elem_size = sizeof(u8), |
455 | + .array_type = NO_ARRAY, | 450 | + .array_type = NO_ARRAY, |
456 | + .tlv_type = 0x12, | 451 | + .tlv_type = 0x12, |
457 | + .offset = offsetof( | 452 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
458 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
459 | + slot_id_valid), | 453 | + slot_id_valid), |
460 | + }, | 454 | + }, |
461 | + { | 455 | + { |
462 | + .data_type = QMI_UNSIGNED_4_BYTE, | 456 | + .data_type = QMI_UNSIGNED_4_BYTE, |
463 | + .elem_len = 1, | 457 | + .elem_len = 1, |
464 | + .elem_size = sizeof(u32), | 458 | + .elem_size = sizeof(u32), |
465 | + .array_type = NO_ARRAY, | 459 | + .array_type = NO_ARRAY, |
466 | + .tlv_type = 0x12, | 460 | + .tlv_type = 0x12, |
467 | + .offset = offsetof( | 461 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
468 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
469 | + slot_id), | 462 | + slot_id), |
470 | + }, | 463 | + }, |
471 | + { | 464 | + { |
472 | + .data_type = QMI_OPT_FLAG, | 465 | + .data_type = QMI_OPT_FLAG, |
473 | + .elem_len = 1, | 466 | + .elem_len = 1, |
474 | + .elem_size = sizeof(u8), | 467 | + .elem_size = sizeof(u8), |
475 | + .array_type = NO_ARRAY, | 468 | + .array_type = NO_ARRAY, |
476 | + .tlv_type = 0x13, | 469 | + .tlv_type = 0x13, |
477 | + .offset = offsetof( | 470 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
478 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
479 | + usb_token_valid), | 471 | + usb_token_valid), |
480 | + }, | 472 | + }, |
481 | + { | 473 | + { |
482 | + .data_type = QMI_UNSIGNED_4_BYTE, | 474 | + .data_type = QMI_UNSIGNED_4_BYTE, |
483 | + .elem_len = 1, | 475 | + .elem_len = 1, |
484 | + .elem_size = sizeof(u32), | 476 | + .elem_size = sizeof(u32), |
485 | + .array_type = NO_ARRAY, | 477 | + .array_type = NO_ARRAY, |
486 | + .tlv_type = 0x13, | 478 | + .tlv_type = 0x13, |
487 | + .offset = offsetof( | 479 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
488 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
489 | + usb_token), | 480 | + usb_token), |
490 | + }, | 481 | + }, |
491 | + { | 482 | + { |
492 | + .data_type = QMI_OPT_FLAG, | 483 | + .data_type = QMI_OPT_FLAG, |
493 | + .elem_len = 1, | 484 | + .elem_len = 1, |
494 | + .elem_size = sizeof(u8), | 485 | + .elem_size = sizeof(u8), |
495 | + .array_type = NO_ARRAY, | 486 | + .array_type = NO_ARRAY, |
496 | + .tlv_type = 0x14, | 487 | + .tlv_type = 0x14, |
497 | + .offset = offsetof( | 488 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
498 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
499 | + std_as_opr_intf_desc_valid), | 489 | + std_as_opr_intf_desc_valid), |
500 | + }, | 490 | + }, |
501 | + { | 491 | + { |
502 | + .data_type = QMI_STRUCT, | 492 | + .data_type = QMI_STRUCT, |
503 | + .elem_len = 1, | 493 | + .elem_len = 1, |
504 | + .elem_size = sizeof(struct usb_interface_descriptor_v01), | 494 | + .elem_size = sizeof(struct usb_interface_descriptor_v01), |
505 | + .array_type = NO_ARRAY, | 495 | + .array_type = NO_ARRAY, |
506 | + .tlv_type = 0x14, | 496 | + .tlv_type = 0x14, |
507 | + .offset = offsetof( | 497 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
508 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
509 | + std_as_opr_intf_desc), | 498 | + std_as_opr_intf_desc), |
510 | + .ei_array = usb_interface_descriptor_v01_ei, | 499 | + .ei_array = usb_interface_descriptor_v01_ei, |
511 | + }, | 500 | + }, |
512 | + { | 501 | + { |
513 | + .data_type = QMI_OPT_FLAG, | 502 | + .data_type = QMI_OPT_FLAG, |
514 | + .elem_len = 1, | 503 | + .elem_len = 1, |
515 | + .elem_size = sizeof(u8), | 504 | + .elem_size = sizeof(u8), |
516 | + .array_type = NO_ARRAY, | 505 | + .array_type = NO_ARRAY, |
517 | + .tlv_type = 0x15, | 506 | + .tlv_type = 0x15, |
518 | + .offset = offsetof( | 507 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
519 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
520 | + std_as_data_ep_desc_valid), | 508 | + std_as_data_ep_desc_valid), |
521 | + }, | 509 | + }, |
522 | + { | 510 | + { |
523 | + .data_type = QMI_STRUCT, | 511 | + .data_type = QMI_STRUCT, |
524 | + .elem_len = 1, | 512 | + .elem_len = 1, |
525 | + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), | 513 | + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), |
526 | + .array_type = NO_ARRAY, | 514 | + .array_type = NO_ARRAY, |
527 | + .tlv_type = 0x15, | 515 | + .tlv_type = 0x15, |
528 | + .offset = offsetof( | 516 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
529 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
530 | + std_as_data_ep_desc), | 517 | + std_as_data_ep_desc), |
531 | + .ei_array = usb_endpoint_descriptor_v01_ei, | 518 | + .ei_array = usb_endpoint_descriptor_v01_ei, |
532 | + }, | 519 | + }, |
533 | + { | 520 | + { |
534 | + .data_type = QMI_OPT_FLAG, | 521 | + .data_type = QMI_OPT_FLAG, |
535 | + .elem_len = 1, | 522 | + .elem_len = 1, |
536 | + .elem_size = sizeof(u8), | 523 | + .elem_size = sizeof(u8), |
537 | + .array_type = NO_ARRAY, | 524 | + .array_type = NO_ARRAY, |
538 | + .tlv_type = 0x16, | 525 | + .tlv_type = 0x16, |
539 | + .offset = offsetof( | 526 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
540 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
541 | + std_as_sync_ep_desc_valid), | 527 | + std_as_sync_ep_desc_valid), |
542 | + }, | 528 | + }, |
543 | + { | 529 | + { |
544 | + .data_type = QMI_STRUCT, | 530 | + .data_type = QMI_STRUCT, |
545 | + .elem_len = 1, | 531 | + .elem_len = 1, |
546 | + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), | 532 | + .elem_size = sizeof(struct usb_endpoint_descriptor_v01), |
547 | + .array_type = NO_ARRAY, | 533 | + .array_type = NO_ARRAY, |
548 | + .tlv_type = 0x16, | 534 | + .tlv_type = 0x16, |
549 | + .offset = offsetof( | 535 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
550 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
551 | + std_as_sync_ep_desc), | 536 | + std_as_sync_ep_desc), |
552 | + .ei_array = usb_endpoint_descriptor_v01_ei, | 537 | + .ei_array = usb_endpoint_descriptor_v01_ei, |
553 | + }, | 538 | + }, |
554 | + { | 539 | + { |
555 | + .data_type = QMI_OPT_FLAG, | 540 | + .data_type = QMI_OPT_FLAG, |
556 | + .elem_len = 1, | 541 | + .elem_len = 1, |
557 | + .elem_size = sizeof(u8), | 542 | + .elem_size = sizeof(u8), |
558 | + .array_type = NO_ARRAY, | 543 | + .array_type = NO_ARRAY, |
559 | + .tlv_type = 0x17, | 544 | + .tlv_type = 0x17, |
560 | + .offset = offsetof( | 545 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
561 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
562 | + usb_audio_spec_revision_valid), | 546 | + usb_audio_spec_revision_valid), |
563 | + }, | 547 | + }, |
564 | + { | 548 | + { |
565 | + .data_type = QMI_UNSIGNED_2_BYTE, | 549 | + .data_type = QMI_UNSIGNED_2_BYTE, |
566 | + .elem_len = 1, | 550 | + .elem_len = 1, |
567 | + .elem_size = sizeof(u16), | 551 | + .elem_size = sizeof(u16), |
568 | + .array_type = NO_ARRAY, | 552 | + .array_type = NO_ARRAY, |
569 | + .tlv_type = 0x17, | 553 | + .tlv_type = 0x17, |
570 | + .offset = offsetof( | 554 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
571 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
572 | + usb_audio_spec_revision), | 555 | + usb_audio_spec_revision), |
573 | + }, | 556 | + }, |
574 | + { | 557 | + { |
575 | + .data_type = QMI_OPT_FLAG, | 558 | + .data_type = QMI_OPT_FLAG, |
576 | + .elem_len = 1, | 559 | + .elem_len = 1, |
577 | + .elem_size = sizeof(u8), | 560 | + .elem_size = sizeof(u8), |
578 | + .array_type = NO_ARRAY, | 561 | + .array_type = NO_ARRAY, |
579 | + .tlv_type = 0x18, | 562 | + .tlv_type = 0x18, |
580 | + .offset = offsetof( | 563 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
581 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
582 | + data_path_delay_valid), | 564 | + data_path_delay_valid), |
583 | + }, | 565 | + }, |
584 | + { | 566 | + { |
585 | + .data_type = QMI_UNSIGNED_1_BYTE, | 567 | + .data_type = QMI_UNSIGNED_1_BYTE, |
586 | + .elem_len = 1, | 568 | + .elem_len = 1, |
587 | + .elem_size = sizeof(u8), | 569 | + .elem_size = sizeof(u8), |
588 | + .array_type = NO_ARRAY, | 570 | + .array_type = NO_ARRAY, |
589 | + .tlv_type = 0x18, | 571 | + .tlv_type = 0x18, |
590 | + .offset = offsetof( | 572 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
591 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
592 | + data_path_delay), | 573 | + data_path_delay), |
593 | + }, | 574 | + }, |
594 | + { | 575 | + { |
595 | + .data_type = QMI_OPT_FLAG, | 576 | + .data_type = QMI_OPT_FLAG, |
596 | + .elem_len = 1, | 577 | + .elem_len = 1, |
597 | + .elem_size = sizeof(u8), | 578 | + .elem_size = sizeof(u8), |
598 | + .array_type = NO_ARRAY, | 579 | + .array_type = NO_ARRAY, |
599 | + .tlv_type = 0x19, | 580 | + .tlv_type = 0x19, |
600 | + .offset = offsetof( | 581 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
601 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
602 | + usb_audio_subslot_size_valid), | 582 | + usb_audio_subslot_size_valid), |
603 | + }, | 583 | + }, |
604 | + { | 584 | + { |
605 | + .data_type = QMI_UNSIGNED_1_BYTE, | 585 | + .data_type = QMI_UNSIGNED_1_BYTE, |
606 | + .elem_len = 1, | 586 | + .elem_len = 1, |
607 | + .elem_size = sizeof(u8), | 587 | + .elem_size = sizeof(u8), |
608 | + .array_type = NO_ARRAY, | 588 | + .array_type = NO_ARRAY, |
609 | + .tlv_type = 0x19, | 589 | + .tlv_type = 0x19, |
610 | + .offset = offsetof( | 590 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
611 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
612 | + usb_audio_subslot_size), | 591 | + usb_audio_subslot_size), |
613 | + }, | 592 | + }, |
614 | + { | 593 | + { |
615 | + .data_type = QMI_OPT_FLAG, | 594 | + .data_type = QMI_OPT_FLAG, |
616 | + .elem_len = 1, | 595 | + .elem_len = 1, |
617 | + .elem_size = sizeof(u8), | 596 | + .elem_size = sizeof(u8), |
618 | + .array_type = NO_ARRAY, | 597 | + .array_type = NO_ARRAY, |
619 | + .tlv_type = 0x1A, | 598 | + .tlv_type = 0x1A, |
620 | + .offset = offsetof( | 599 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
621 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
622 | + xhci_mem_info_valid), | 600 | + xhci_mem_info_valid), |
623 | + }, | 601 | + }, |
624 | + { | 602 | + { |
625 | + .data_type = QMI_STRUCT, | 603 | + .data_type = QMI_STRUCT, |
626 | + .elem_len = 1, | 604 | + .elem_len = 1, |
627 | + .elem_size = sizeof(struct apps_mem_info_v01), | 605 | + .elem_size = sizeof(struct apps_mem_info_v01), |
628 | + .array_type = NO_ARRAY, | 606 | + .array_type = NO_ARRAY, |
629 | + .tlv_type = 0x1A, | 607 | + .tlv_type = 0x1A, |
630 | + .offset = offsetof( | 608 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
631 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
632 | + xhci_mem_info), | 609 | + xhci_mem_info), |
633 | + .ei_array = apps_mem_info_v01_ei, | 610 | + .ei_array = apps_mem_info_v01_ei, |
634 | + }, | 611 | + }, |
635 | + { | 612 | + { |
636 | + .data_type = QMI_OPT_FLAG, | 613 | + .data_type = QMI_OPT_FLAG, |
637 | + .elem_len = 1, | 614 | + .elem_len = 1, |
638 | + .elem_size = sizeof(u8), | 615 | + .elem_size = sizeof(u8), |
639 | + .array_type = NO_ARRAY, | 616 | + .array_type = NO_ARRAY, |
640 | + .tlv_type = 0x1B, | 617 | + .tlv_type = 0x1B, |
641 | + .offset = offsetof( | 618 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
642 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
643 | + interrupter_num_valid), | 619 | + interrupter_num_valid), |
644 | + }, | 620 | + }, |
645 | + { | 621 | + { |
646 | + .data_type = QMI_UNSIGNED_1_BYTE, | 622 | + .data_type = QMI_UNSIGNED_1_BYTE, |
647 | + .elem_len = 1, | 623 | + .elem_len = 1, |
648 | + .elem_size = sizeof(u8), | 624 | + .elem_size = sizeof(u8), |
649 | + .array_type = NO_ARRAY, | 625 | + .array_type = NO_ARRAY, |
650 | + .tlv_type = 0x1B, | 626 | + .tlv_type = 0x1B, |
651 | + .offset = offsetof( | 627 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
652 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
653 | + interrupter_num), | 628 | + interrupter_num), |
654 | + }, | 629 | + }, |
655 | + { | 630 | + { |
656 | + .data_type = QMI_OPT_FLAG, | 631 | + .data_type = QMI_OPT_FLAG, |
657 | + .elem_len = 1, | 632 | + .elem_len = 1, |
658 | + .elem_size = sizeof(u8), | 633 | + .elem_size = sizeof(u8), |
659 | + .array_type = NO_ARRAY, | 634 | + .array_type = NO_ARRAY, |
660 | + .tlv_type = 0x1C, | 635 | + .tlv_type = 0x1C, |
661 | + .offset = offsetof( | 636 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
662 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
663 | + speed_info_valid), | 637 | + speed_info_valid), |
664 | + }, | 638 | + }, |
665 | + { | 639 | + { |
666 | + .data_type = QMI_SIGNED_4_BYTE_ENUM, | 640 | + .data_type = QMI_SIGNED_4_BYTE_ENUM, |
667 | + .elem_len = 1, | 641 | + .elem_len = 1, |
668 | + .elem_size = sizeof(enum usb_qmi_audio_device_speed_enum_v01), | 642 | + .elem_size = sizeof(enum usb_qmi_audio_device_speed_enum_v01), |
669 | + .array_type = NO_ARRAY, | 643 | + .array_type = NO_ARRAY, |
670 | + .tlv_type = 0x1C, | 644 | + .tlv_type = 0x1C, |
671 | + .offset = offsetof( | 645 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
672 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
673 | + speed_info), | 646 | + speed_info), |
674 | + }, | 647 | + }, |
675 | + { | 648 | + { |
676 | + .data_type = QMI_OPT_FLAG, | 649 | + .data_type = QMI_OPT_FLAG, |
677 | + .elem_len = 1, | 650 | + .elem_len = 1, |
678 | + .elem_size = sizeof(u8), | 651 | + .elem_size = sizeof(u8), |
679 | + .array_type = NO_ARRAY, | 652 | + .array_type = NO_ARRAY, |
680 | + .tlv_type = 0x1D, | 653 | + .tlv_type = 0x1D, |
681 | + .offset = offsetof( | 654 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
682 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
683 | + controller_num_valid), | 655 | + controller_num_valid), |
684 | + }, | 656 | + }, |
685 | + { | 657 | + { |
686 | + .data_type = QMI_UNSIGNED_1_BYTE, | 658 | + .data_type = QMI_UNSIGNED_1_BYTE, |
687 | + .elem_len = 1, | 659 | + .elem_len = 1, |
688 | + .elem_size = sizeof(u8), | 660 | + .elem_size = sizeof(u8), |
689 | + .array_type = NO_ARRAY, | 661 | + .array_type = NO_ARRAY, |
690 | + .tlv_type = 0x1D, | 662 | + .tlv_type = 0x1D, |
691 | + .offset = offsetof( | 663 | + .offset = offsetof(struct qmi_uaudio_stream_resp_msg_v01, |
692 | + struct qmi_uaudio_stream_resp_msg_v01, | ||
693 | + controller_num), | 664 | + controller_num), |
694 | + }, | 665 | + }, |
695 | + { | 666 | + { |
696 | + .data_type = QMI_EOTI, | 667 | + .data_type = QMI_EOTI, |
697 | + .array_type = NO_ARRAY, | 668 | + .array_type = NO_ARRAY, |
698 | + .tlv_type = QMI_COMMON_TLV_TYPE, | 669 | + .tlv_type = QMI_COMMON_TLV_TYPE, |
699 | + }, | 670 | + }, |
700 | +}; | 671 | +}; |
701 | + | 672 | + |
702 | +struct qmi_elem_info qmi_uaudio_stream_ind_msg_v01_ei[] = { | 673 | +const struct qmi_elem_info qmi_uaudio_stream_ind_msg_v01_ei[] = { |
703 | + { | 674 | + { |
704 | + .data_type = QMI_SIGNED_4_BYTE_ENUM, | 675 | + .data_type = QMI_SIGNED_4_BYTE_ENUM, |
705 | + .elem_len = 1, | 676 | + .elem_len = 1, |
706 | + .elem_size = sizeof( | 677 | + .elem_size = sizeof( |
707 | + enum usb_qmi_audio_device_indication_enum_v01), | 678 | + enum usb_qmi_audio_device_indication_enum_v01), |
... | ... | ||
915 | --- /dev/null | 886 | --- /dev/null |
916 | +++ b/sound/usb/qcom/usb_audio_qmi_v01.h | 887 | +++ b/sound/usb/qcom/usb_audio_qmi_v01.h |
917 | @@ -XXX,XX +XXX,XX @@ | 888 | @@ -XXX,XX +XXX,XX @@ |
918 | +/* SPDX-License-Identifier: GPL-2.0 | 889 | +/* SPDX-License-Identifier: GPL-2.0 |
919 | + * | 890 | + * |
920 | + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. | 891 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. |
921 | + */ | 892 | + */ |
922 | + | 893 | + |
923 | +#ifndef USB_QMI_V01_H | 894 | +#ifndef USB_QMI_V01_H |
924 | +#define USB_QMI_V01_H | 895 | +#define USB_QMI_V01_H |
925 | + | 896 | + |
926 | +#define UAUDIO_STREAM_SERVICE_ID_V01 0x41D | 897 | +#define UAUDIO_STREAM_SERVICE_ID_V01 0x41D |
927 | +#define UAUDIO_STREAM_SERVICE_VERS_V01 0x01 | 898 | +#define UAUDIO_STREAM_SERVICE_VERS_V01 0x01 |
928 | + | 899 | + |
929 | +#define QMI_UAUDIO_STREAM_RESP_V01 0x0001 | 900 | +#define QMI_UAUDIO_STREAM_RESP_V01 0x0001 |
930 | +#define QMI_UAUDIO_STREAM_REQ_V01 0x0001 | 901 | +#define QMI_UAUDIO_STREAM_REQ_V01 0x0001 |
931 | +#define QMI_UAUDIO_STREAM_IND_V01 0x0001 | 902 | +#define QMI_UAUDIO_STREAM_IND_V01 0x0001 |
932 | + | ||
933 | + | 903 | + |
934 | +struct mem_info_v01 { | 904 | +struct mem_info_v01 { |
935 | + u64 va; | 905 | + u64 va; |
936 | + u64 pa; | 906 | + u64 pa; |
937 | + u32 size; | 907 | + u32 size; |
... | ... | ||
1010 | + u8 xfer_buff_size_valid; | 980 | + u8 xfer_buff_size_valid; |
1011 | + u32 xfer_buff_size; | 981 | + u32 xfer_buff_size; |
1012 | + u8 service_interval_valid; | 982 | + u8 service_interval_valid; |
1013 | + u32 service_interval; | 983 | + u32 service_interval; |
1014 | +}; | 984 | +}; |
985 | + | ||
1015 | +#define QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN 46 | 986 | +#define QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN 46 |
1016 | +extern struct qmi_elem_info qmi_uaudio_stream_req_msg_v01_ei[]; | 987 | +extern const struct qmi_elem_info qmi_uaudio_stream_req_msg_v01_ei[]; |
1017 | + | 988 | + |
1018 | +struct qmi_uaudio_stream_resp_msg_v01 { | 989 | +struct qmi_uaudio_stream_resp_msg_v01 { |
1019 | + struct qmi_response_type_v01 resp; | 990 | + struct qmi_response_type_v01 resp; |
1020 | + u8 status_valid; | 991 | + u8 status_valid; |
1021 | + enum usb_qmi_audio_stream_status_enum_v01 status; | 992 | + enum usb_qmi_audio_stream_status_enum_v01 status; |
... | ... | ||
1044 | + u8 speed_info_valid; | 1015 | + u8 speed_info_valid; |
1045 | + enum usb_qmi_audio_device_speed_enum_v01 speed_info; | 1016 | + enum usb_qmi_audio_device_speed_enum_v01 speed_info; |
1046 | + u8 controller_num_valid; | 1017 | + u8 controller_num_valid; |
1047 | + u8 controller_num; | 1018 | + u8 controller_num; |
1048 | +}; | 1019 | +}; |
1020 | + | ||
1049 | +#define QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN 202 | 1021 | +#define QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN 202 |
1050 | +extern struct qmi_elem_info qmi_uaudio_stream_resp_msg_v01_ei[]; | 1022 | +extern const struct qmi_elem_info qmi_uaudio_stream_resp_msg_v01_ei[]; |
1051 | + | 1023 | + |
1052 | +struct qmi_uaudio_stream_ind_msg_v01 { | 1024 | +struct qmi_uaudio_stream_ind_msg_v01 { |
1053 | + enum usb_qmi_audio_device_indication_enum_v01 dev_event; | 1025 | + enum usb_qmi_audio_device_indication_enum_v01 dev_event; |
1054 | + u32 slot_id; | 1026 | + u32 slot_id; |
1055 | + u8 usb_token_valid; | 1027 | + u8 usb_token_valid; |
... | ... | ||
1071 | + u8 interrupter_num_valid; | 1043 | + u8 interrupter_num_valid; |
1072 | + u8 interrupter_num; | 1044 | + u8 interrupter_num; |
1073 | + u8 controller_num_valid; | 1045 | + u8 controller_num_valid; |
1074 | + u8 controller_num; | 1046 | + u8 controller_num; |
1075 | +}; | 1047 | +}; |
1048 | + | ||
1076 | +#define QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN 181 | 1049 | +#define QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN 181 |
1077 | +extern struct qmi_elem_info qmi_uaudio_stream_ind_msg_v01_ei[]; | 1050 | +extern const struct qmi_elem_info qmi_uaudio_stream_ind_msg_v01_ei[]; |
1078 | + | 1051 | + |
1079 | +#endif | 1052 | +#endif | diff view generated by jsdifflib |
... | ... | ||
---|---|---|---|
23 | 4. The QC audio offload driver will fetch the required resources, and pass | 23 | 4. The QC audio offload driver will fetch the required resources, and pass |
24 | this information as part of the QMI response to the STREAM enable command. | 24 | this information as part of the QMI response to the STREAM enable command. |
25 | 5. Once the QMI response is received the audio DSP will start queuing data | 25 | 5. Once the QMI response is received the audio DSP will start queuing data |
26 | on the USB bus. | 26 | on the USB bus. |
27 | 27 | ||
28 | As part of step#2, the audio DSP is aware of the USB SND card and pcm | ||
29 | device index that is being selected, and is communicated as part of the QMI | ||
30 | request received by QC audio offload. These indices will be used to handle | ||
31 | the stream enable QMI request. | ||
32 | |||
28 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 33 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
29 | --- | 34 | --- |
30 | include/sound/soc-usb.h | 2 +- | 35 | sound/usb/Kconfig | 14 + |
31 | sound/usb/Kconfig | 15 + | ||
32 | sound/usb/Makefile | 2 +- | 36 | sound/usb/Makefile | 2 +- |
33 | sound/usb/qcom/Makefile | 2 + | 37 | sound/usb/qcom/Makefile | 2 + |
34 | sound/usb/qcom/qc_audio_offload.c | 1843 +++++++++++++++++++++++++++++ | 38 | sound/usb/qcom/qc_audio_offload.c | 1988 +++++++++++++++++++++++++++++ |
35 | 5 files changed, 1862 insertions(+), 2 deletions(-) | 39 | 4 files changed, 2005 insertions(+), 1 deletion(-) |
36 | create mode 100644 sound/usb/qcom/Makefile | 40 | create mode 100644 sound/usb/qcom/Makefile |
37 | create mode 100644 sound/usb/qcom/qc_audio_offload.c | 41 | create mode 100644 sound/usb/qcom/qc_audio_offload.c |
38 | 42 | ||
39 | diff --git a/include/sound/soc-usb.h b/include/sound/soc-usb.h | ||
40 | index XXXXXXX..XXXXXXX 100644 | ||
41 | --- a/include/sound/soc-usb.h | ||
42 | +++ b/include/sound/soc-usb.h | ||
43 | @@ -XXX,XX +XXX,XX @@ struct snd_soc_usb { | ||
44 | |||
45 | int snd_soc_usb_connect(struct device *usbdev, struct snd_soc_usb_device *sdev); | ||
46 | int snd_soc_usb_disconnect(struct device *usbdev, struct snd_soc_usb_device *sdev); | ||
47 | -void *snd_soc_usb_get_priv_data(struct device *usbdev); | ||
48 | +void *snd_soc_usb_find_priv_data(struct device *usbdev); | ||
49 | |||
50 | struct snd_soc_usb *snd_soc_usb_add_port(struct device *dev, void *priv, | ||
51 | int (*connection_cb)(struct snd_soc_usb *usb, | ||
52 | diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig | 43 | diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig |
53 | index XXXXXXX..XXXXXXX 100644 | 44 | index XXXXXXX..XXXXXXX 100644 |
54 | --- a/sound/usb/Kconfig | 45 | --- a/sound/usb/Kconfig |
55 | +++ b/sound/usb/Kconfig | 46 | +++ b/sound/usb/Kconfig |
56 | @@ -XXX,XX +XXX,XX @@ config SND_BCD2000 | 47 | @@ -XXX,XX +XXX,XX @@ config SND_BCD2000 |
57 | To compile this driver as a module, choose M here: the module | 48 | To compile this driver as a module, choose M here: the module |
58 | will be called snd-bcd2000. | 49 | will be called snd-bcd2000. |
59 | 50 | ||
60 | +config SND_USB_AUDIO_QMI | 51 | +config SND_USB_AUDIO_QMI |
61 | + tristate "Qualcomm Audio Offload driver" | 52 | + tristate "Qualcomm Audio Offload driver" |
62 | + depends on QCOM_QMI_HELPERS && SND_USB_AUDIO && USB_XHCI_SIDEBAND | 53 | + depends on QCOM_QMI_HELPERS && SND_USB_AUDIO && USB_XHCI_SIDEBAND && SND_SOC_USB |
63 | + select SND_PCM | ||
64 | + help | 54 | + help |
65 | + Say Y here to enable the Qualcomm USB audio offloading feature. | 55 | + Say Y here to enable the Qualcomm USB audio offloading feature. |
66 | + | 56 | + |
67 | + This module sets up the required QMI stream enable/disable | 57 | + This module sets up the required QMI stream enable/disable |
68 | + responses to requests generated by the audio DSP. It passes the | 58 | + responses to requests generated by the audio DSP. It passes the |
... | ... | ||
90 | new file mode 100644 | 80 | new file mode 100644 |
91 | index XXXXXXX..XXXXXXX | 81 | index XXXXXXX..XXXXXXX |
92 | --- /dev/null | 82 | --- /dev/null |
93 | +++ b/sound/usb/qcom/Makefile | 83 | +++ b/sound/usb/qcom/Makefile |
94 | @@ -XXX,XX +XXX,XX @@ | 84 | @@ -XXX,XX +XXX,XX @@ |
95 | +snd-usb-audio-qmi-objs := usb_audio_qmi_v01.o qc_audio_offload.o | 85 | +snd-usb-audio-qmi-y := usb_audio_qmi_v01.o qc_audio_offload.o |
96 | +obj-$(CONFIG_SND_USB_AUDIO_QMI) += snd-usb-audio-qmi.o | 86 | +obj-$(CONFIG_SND_USB_AUDIO_QMI) += snd-usb-audio-qmi.o |
97 | \ No newline at end of file | ||
98 | diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c | 87 | diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c |
99 | new file mode 100644 | 88 | new file mode 100644 |
100 | index XXXXXXX..XXXXXXX | 89 | index XXXXXXX..XXXXXXX |
101 | --- /dev/null | 90 | --- /dev/null |
102 | +++ b/sound/usb/qcom/qc_audio_offload.c | 91 | +++ b/sound/usb/qcom/qc_audio_offload.c |
103 | @@ -XXX,XX +XXX,XX @@ | 92 | @@ -XXX,XX +XXX,XX @@ |
104 | +// SPDX-License-Identifier: GPL-2.0 | 93 | +// SPDX-License-Identifier: GPL-2.0 |
105 | +/* | 94 | +/* |
106 | + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. | 95 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. |
107 | + */ | 96 | + */ |
108 | + | 97 | + |
109 | +#include <linux/ctype.h> | 98 | +#include <linux/ctype.h> |
99 | +#include <linux/dma-mapping.h> | ||
100 | +#include <linux/dma-map-ops.h> | ||
101 | +#include <linux/init.h> | ||
102 | +#include <linux/iommu.h> | ||
103 | +#include <linux/module.h> | ||
110 | +#include <linux/moduleparam.h> | 104 | +#include <linux/moduleparam.h> |
111 | +#include <linux/module.h> | 105 | +#include <linux/soc/qcom/qmi.h> |
112 | +#include <linux/usb.h> | 106 | +#include <linux/usb.h> |
113 | +#include <linux/init.h> | ||
114 | +#include <linux/usb/hcd.h> | ||
115 | +#include <linux/usb/xhci-sideband.h> | ||
116 | +#include <linux/usb/quirks.h> | ||
117 | +#include <linux/usb/audio.h> | 107 | +#include <linux/usb/audio.h> |
118 | +#include <linux/usb/audio-v2.h> | 108 | +#include <linux/usb/audio-v2.h> |
119 | +#include <linux/usb/audio-v3.h> | 109 | +#include <linux/usb/audio-v3.h> |
120 | +#include <linux/soc/qcom/qmi.h> | 110 | +#include <linux/usb/hcd.h> |
121 | +#include <linux/iommu.h> | 111 | +#include <linux/usb/quirks.h> |
122 | +#include <linux/dma-mapping.h> | 112 | +#include <linux/usb/xhci-sideband.h> |
123 | +#include <linux/dma-map-ops.h> | ||
124 | +#include <sound/q6usboffload.h> | ||
125 | + | 113 | + |
126 | +#include <sound/control.h> | 114 | +#include <sound/control.h> |
127 | +#include <sound/core.h> | 115 | +#include <sound/core.h> |
128 | +#include <sound/info.h> | 116 | +#include <sound/info.h> |
117 | +#include <sound/initval.h> | ||
129 | +#include <sound/pcm.h> | 118 | +#include <sound/pcm.h> |
130 | +#include <sound/pcm_params.h> | 119 | +#include <sound/pcm_params.h> |
131 | +#include <sound/initval.h> | 120 | +#include <sound/q6usboffload.h> |
132 | + | ||
133 | +#include <sound/soc.h> | 121 | +#include <sound/soc.h> |
134 | +#include <sound/soc-usb.h> | 122 | +#include <sound/soc-usb.h> |
123 | + | ||
135 | +#include "../usbaudio.h" | 124 | +#include "../usbaudio.h" |
136 | +#include "../card.h" | 125 | +#include "../card.h" |
137 | +#include "../endpoint.h" | 126 | +#include "../endpoint.h" |
127 | +#include "../format.h" | ||
138 | +#include "../helper.h" | 128 | +#include "../helper.h" |
139 | +#include "../pcm.h" | 129 | +#include "../pcm.h" |
140 | +#include "../format.h" | ||
141 | +#include "../power.h" | 130 | +#include "../power.h" |
131 | + | ||
142 | +#include "usb_audio_qmi_v01.h" | 132 | +#include "usb_audio_qmi_v01.h" |
143 | + | 133 | + |
144 | +/* Stream disable request timeout during USB device disconnect */ | 134 | +/* Stream disable request timeout during USB device disconnect */ |
145 | +#define DEV_RELEASE_WAIT_TIMEOUT 10000 /* in ms */ | 135 | +#define DEV_RELEASE_WAIT_TIMEOUT 10000 /* in ms */ |
146 | + | 136 | + |
... | ... | ||
154 | +#define QMI_STREAM_REQ_DIRECTION 0xff | 144 | +#define QMI_STREAM_REQ_DIRECTION 0xff |
155 | + | 145 | + |
156 | +/* iommu resource parameters and management */ | 146 | +/* iommu resource parameters and management */ |
157 | +#define PREPEND_SID_TO_IOVA(iova, sid) ((u64)(((u64)(iova)) | \ | 147 | +#define PREPEND_SID_TO_IOVA(iova, sid) ((u64)(((u64)(iova)) | \ |
158 | + (((u64)sid) << 32))) | 148 | + (((u64)sid) << 32))) |
149 | +#define IOVA_MASK(iova) (((u64)(iova)) & 0xFFFFFFFF) | ||
159 | +#define IOVA_BASE 0x1000 | 150 | +#define IOVA_BASE 0x1000 |
160 | +#define IOVA_XFER_RING_BASE (IOVA_BASE + PAGE_SIZE * (SNDRV_CARDS + 1)) | 151 | +#define IOVA_XFER_RING_BASE (IOVA_BASE + PAGE_SIZE * (SNDRV_CARDS + 1)) |
161 | +#define IOVA_XFER_BUF_BASE (IOVA_XFER_RING_BASE + PAGE_SIZE * SNDRV_CARDS * 32) | 152 | +#define IOVA_XFER_BUF_BASE (IOVA_XFER_RING_BASE + PAGE_SIZE * SNDRV_CARDS * 32) |
162 | +#define IOVA_XFER_RING_MAX (IOVA_XFER_BUF_BASE - PAGE_SIZE) | 153 | +#define IOVA_XFER_RING_MAX (IOVA_XFER_BUF_BASE - PAGE_SIZE) |
163 | +#define IOVA_XFER_BUF_MAX (0xfffff000 - PAGE_SIZE) | 154 | +#define IOVA_XFER_BUF_MAX (0xfffff000 - PAGE_SIZE) |
... | ... | ||
170 | + size_t size; | 161 | + size_t size; |
171 | + bool in_use; | 162 | + bool in_use; |
172 | +}; | 163 | +}; |
173 | + | 164 | + |
174 | +struct intf_info { | 165 | +struct intf_info { |
166 | + /* IOMMU ring/buffer mapping information */ | ||
175 | + unsigned long data_xfer_ring_va; | 167 | + unsigned long data_xfer_ring_va; |
176 | + size_t data_xfer_ring_size; | 168 | + size_t data_xfer_ring_size; |
177 | + unsigned long sync_xfer_ring_va; | 169 | + unsigned long sync_xfer_ring_va; |
178 | + size_t sync_xfer_ring_size; | 170 | + size_t sync_xfer_ring_size; |
179 | + unsigned long xfer_buf_va; | 171 | + unsigned long xfer_buf_va; |
180 | + size_t xfer_buf_size; | 172 | + size_t xfer_buf_size; |
181 | + phys_addr_t xfer_buf_pa; | 173 | + phys_addr_t xfer_buf_pa; |
174 | + u8 *xfer_buf; | ||
175 | + | ||
176 | + /* USB endpoint information */ | ||
182 | + unsigned int data_ep_pipe; | 177 | + unsigned int data_ep_pipe; |
183 | + unsigned int sync_ep_pipe; | 178 | + unsigned int sync_ep_pipe; |
184 | + u8 *xfer_buf; | 179 | + unsigned int data_ep_idx; |
180 | + unsigned int sync_ep_idx; | ||
181 | + | ||
185 | + u8 intf_num; | 182 | + u8 intf_num; |
186 | + u8 pcm_card_num; | 183 | + u8 pcm_card_num; |
187 | + u8 pcm_dev_num; | 184 | + u8 pcm_dev_num; |
188 | + u8 direction; | 185 | + u8 direction; |
189 | + bool in_use; | 186 | + bool in_use; |
190 | +}; | 187 | +}; |
191 | + | 188 | + |
192 | +struct uaudio_qmi_dev { | 189 | +struct uaudio_qmi_dev { |
193 | + struct device *dev; | 190 | + struct device *dev; |
194 | + u32 sid; | 191 | + struct q6usb_offload *data; |
195 | + u32 intr_num; | ||
196 | + struct xhci_ring *sec_ring; | ||
197 | + struct iommu_domain *domain; | ||
198 | + | 192 | + |
199 | + /* list to keep track of available iova */ | 193 | + /* list to keep track of available iova */ |
200 | + struct list_head xfer_ring_list; | 194 | + struct list_head xfer_ring_list; |
201 | + size_t xfer_ring_iova_size; | 195 | + size_t xfer_ring_iova_size; |
202 | + unsigned long curr_xfer_ring_iova; | 196 | + unsigned long curr_xfer_ring_iova; |
... | ... | ||
208 | + unsigned long card_slot; | 202 | + unsigned long card_slot; |
209 | + /* indicate event ring mapped or not */ | 203 | + /* indicate event ring mapped or not */ |
210 | + bool er_mapped; | 204 | + bool er_mapped; |
211 | + /* reference count to number of possible consumers */ | 205 | + /* reference count to number of possible consumers */ |
212 | + atomic_t qdev_in_use; | 206 | + atomic_t qdev_in_use; |
213 | + /* idx to last udev card number plugged in */ | ||
214 | + unsigned int last_card_num; | ||
215 | +}; | 207 | +}; |
216 | + | 208 | + |
217 | +struct uaudio_dev { | 209 | +struct uaudio_dev { |
218 | + struct usb_device *udev; | 210 | + struct usb_device *udev; |
219 | + /* audio control interface */ | 211 | + /* audio control interface */ |
... | ... | ||
240 | +static struct uaudio_qmi_svc *uaudio_svc; | 232 | +static struct uaudio_qmi_svc *uaudio_svc; |
241 | +static DEFINE_MUTEX(qdev_mutex); | 233 | +static DEFINE_MUTEX(qdev_mutex); |
242 | + | 234 | + |
243 | +struct uaudio_qmi_svc { | 235 | +struct uaudio_qmi_svc { |
244 | + struct qmi_handle *uaudio_svc_hdl; | 236 | + struct qmi_handle *uaudio_svc_hdl; |
245 | + struct work_struct qmi_disconnect_work; | ||
246 | + struct workqueue_struct *uaudio_wq; | ||
247 | + struct sockaddr_qrtr client_sq; | 237 | + struct sockaddr_qrtr client_sq; |
248 | + bool client_connected; | 238 | + bool client_connected; |
249 | +}; | 239 | +}; |
250 | + | 240 | + |
251 | +enum mem_type { | 241 | +enum mem_type { |
... | ... | ||
274 | + USB_QMI_PCM_FORMAT_S32_BE, | 264 | + USB_QMI_PCM_FORMAT_S32_BE, |
275 | + USB_QMI_PCM_FORMAT_U32_LE, | 265 | + USB_QMI_PCM_FORMAT_U32_LE, |
276 | + USB_QMI_PCM_FORMAT_U32_BE, | 266 | + USB_QMI_PCM_FORMAT_U32_BE, |
277 | +}; | 267 | +}; |
278 | + | 268 | + |
269 | +static int usb_qmi_get_pcm_num(struct snd_usb_audio *chip, int direction) | ||
270 | +{ | ||
271 | + struct snd_usb_substream *subs = NULL; | ||
272 | + struct snd_usb_stream *as; | ||
273 | + int count = 0; | ||
274 | + | ||
275 | + list_for_each_entry(as, &chip->pcm_list, list) { | ||
276 | + subs = &as->substream[direction]; | ||
277 | + if (subs->ep_num) | ||
278 | + count++; | ||
279 | + } | ||
280 | + | ||
281 | + return count; | ||
282 | +} | ||
283 | + | ||
279 | +static enum usb_qmi_audio_device_speed_enum_v01 | 284 | +static enum usb_qmi_audio_device_speed_enum_v01 |
280 | +get_speed_info(enum usb_device_speed udev_speed) | 285 | +get_speed_info(enum usb_device_speed udev_speed) |
281 | +{ | 286 | +{ |
282 | + switch (udev_speed) { | 287 | + switch (udev_speed) { |
283 | + case USB_SPEED_LOW: | 288 | + case USB_SPEED_LOW: |
... | ... | ||
294 | + return USB_QMI_DEVICE_SPEED_INVALID_V01; | 299 | + return USB_QMI_DEVICE_SPEED_INVALID_V01; |
295 | + } | 300 | + } |
296 | +} | 301 | +} |
297 | + | 302 | + |
298 | +static struct snd_usb_substream *find_substream(unsigned int card_num, | 303 | +static struct snd_usb_substream *find_substream(unsigned int card_num, |
299 | + unsigned int pcm_idx, unsigned int direction) | 304 | + unsigned int pcm_idx, |
300 | +{ | 305 | + unsigned int direction) |
301 | + struct snd_usb_stream *as; | 306 | +{ |
302 | + struct snd_usb_substream *subs = NULL; | 307 | + struct snd_usb_substream *subs = NULL; |
303 | + struct snd_usb_audio *chip; | 308 | + struct snd_usb_audio *chip; |
309 | + struct snd_usb_stream *as; | ||
304 | + | 310 | + |
305 | + chip = uadev[card_num].chip; | 311 | + chip = uadev[card_num].chip; |
306 | + if (!chip || atomic_read(&chip->shutdown)) | 312 | + if (!chip || atomic_read(&chip->shutdown)) |
307 | + goto done; | 313 | + goto done; |
308 | + | 314 | + |
... | ... | ||
336 | + | 342 | + |
337 | + for (i = 0; i < uadev[card_num].num_intf; i++) { | 343 | + for (i = 0; i < uadev[card_num].num_intf; i++) { |
338 | + if (enable && !uadev[card_num].info[i].in_use) | 344 | + if (enable && !uadev[card_num].info[i].in_use) |
339 | + return i; | 345 | + return i; |
340 | + else if (!enable && | 346 | + else if (!enable && |
341 | + uadev[card_num].info[i].intf_num == intf_num) | 347 | + uadev[card_num].info[i].intf_num == intf_num) |
342 | + return i; | 348 | + return i; |
343 | + } | 349 | + } |
344 | + | 350 | + |
345 | + return -EINVAL; | 351 | + return -EINVAL; |
346 | +} | 352 | +} |
347 | + | 353 | + |
348 | +static int get_data_interval_from_si(struct snd_usb_substream *subs, | 354 | +static int get_data_interval_from_si(struct snd_usb_substream *subs, |
349 | + u32 service_interval) | 355 | + u32 service_interval) |
350 | +{ | 356 | +{ |
351 | + unsigned int bus_intval, bus_intval_mult, binterval; | 357 | + unsigned int bus_intval_mult; |
358 | + unsigned int bus_intval; | ||
359 | + unsigned int binterval; | ||
352 | + | 360 | + |
353 | + if (subs->dev->speed >= USB_SPEED_HIGH) | 361 | + if (subs->dev->speed >= USB_SPEED_HIGH) |
354 | + bus_intval = BUS_INTERVAL_HIGHSPEED_AND_ABOVE; | 362 | + bus_intval = BUS_INTERVAL_HIGHSPEED_AND_ABOVE; |
355 | + else | 363 | + else |
356 | + bus_intval = BUS_INTERVAL_FULL_SPEED; | 364 | + bus_intval = BUS_INTERVAL_FULL_SPEED; |
... | ... | ||
420 | + */ | 428 | + */ |
421 | + return SNDRV_PCM_FORMAT_S8; | 429 | + return SNDRV_PCM_FORMAT_S8; |
422 | + } | 430 | + } |
423 | +} | 431 | +} |
424 | + | 432 | + |
433 | +/* | ||
434 | + * Sends QMI disconnect indication message, assumes chip->mutex and qdev_mutex | ||
435 | + * lock held by caller. | ||
436 | + */ | ||
437 | +static int uaudio_send_disconnect_ind(struct snd_usb_audio *chip) | ||
438 | +{ | ||
439 | + struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; | ||
440 | + struct uaudio_qmi_svc *svc = uaudio_svc; | ||
441 | + struct uaudio_dev *dev; | ||
442 | + int ret = 0; | ||
443 | + | ||
444 | + dev = &uadev[chip->card->number]; | ||
445 | + | ||
446 | + if (atomic_read(&dev->in_use)) { | ||
447 | + mutex_unlock(&chip->mutex); | ||
448 | + mutex_unlock(&qdev_mutex); | ||
449 | + dev_dbg(uaudio_qdev->data->dev, "sending qmi indication suspend\n"); | ||
450 | + disconnect_ind.dev_event = USB_QMI_DEV_DISCONNECT_V01; | ||
451 | + disconnect_ind.slot_id = dev->udev->slot_id; | ||
452 | + disconnect_ind.controller_num = dev->usb_core_id; | ||
453 | + disconnect_ind.controller_num_valid = 1; | ||
454 | + ret = qmi_send_indication(svc->uaudio_svc_hdl, &svc->client_sq, | ||
455 | + QMI_UAUDIO_STREAM_IND_V01, | ||
456 | + QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, | ||
457 | + qmi_uaudio_stream_ind_msg_v01_ei, | ||
458 | + &disconnect_ind); | ||
459 | + if (ret < 0) | ||
460 | + dev_err(uaudio_qdev->data->dev, | ||
461 | + "qmi send failed with err: %d\n", ret); | ||
462 | + | ||
463 | + ret = wait_event_interruptible_timeout(dev->disconnect_wq, | ||
464 | + !atomic_read(&dev->in_use), | ||
465 | + msecs_to_jiffies(DEV_RELEASE_WAIT_TIMEOUT)); | ||
466 | + if (!ret) { | ||
467 | + dev_err(uaudio_qdev->data->dev, | ||
468 | + "timeout while waiting for dev_release\n"); | ||
469 | + atomic_set(&dev->in_use, 0); | ||
470 | + } else if (ret < 0) { | ||
471 | + dev_err(uaudio_qdev->data->dev, | ||
472 | + "failed with ret %d\n", ret); | ||
473 | + atomic_set(&dev->in_use, 0); | ||
474 | + } | ||
475 | + mutex_lock(&qdev_mutex); | ||
476 | + mutex_lock(&chip->mutex); | ||
477 | + } | ||
478 | + | ||
479 | + return ret; | ||
480 | +} | ||
481 | + | ||
425 | +/* Offloading IOMMU management */ | 482 | +/* Offloading IOMMU management */ |
426 | +static unsigned long uaudio_get_iova(unsigned long *curr_iova, | 483 | +static unsigned long uaudio_get_iova(unsigned long *curr_iova, |
427 | + size_t *curr_iova_size, struct list_head *head, size_t size) | 484 | + size_t *curr_iova_size, |
485 | + struct list_head *head, size_t size) | ||
428 | +{ | 486 | +{ |
429 | + struct iova_info *info, *new_info = NULL; | 487 | + struct iova_info *info, *new_info = NULL; |
430 | + struct list_head *curr_head; | 488 | + struct list_head *curr_head; |
489 | + size_t tmp_size = size; | ||
431 | + unsigned long va = 0; | 490 | + unsigned long va = 0; |
432 | + size_t tmp_size = size; | 491 | + |
433 | + bool found = false; | 492 | + if (size % PAGE_SIZE) |
434 | + | ||
435 | + if (size % PAGE_SIZE) { | ||
436 | + dev_dbg(uaudio_qdev->dev, "size %zu is not page size multiple\n", | ||
437 | + size); | ||
438 | + goto done; | 493 | + goto done; |
439 | + } | 494 | + |
440 | + | 495 | + if (size > *curr_iova_size) |
441 | + if (size > *curr_iova_size) { | ||
442 | + dev_dbg(uaudio_qdev->dev, "size %zu > curr size %zu\n", | ||
443 | + size, *curr_iova_size); | ||
444 | + goto done; | 496 | + goto done; |
445 | + } | 497 | + |
446 | + if (*curr_iova_size == 0) { | 498 | + if (*curr_iova_size == 0) |
447 | + dev_dbg(uaudio_qdev->dev, "iova mapping is full\n"); | ||
448 | + goto done; | 499 | + goto done; |
449 | + } | ||
450 | + | 500 | + |
451 | + list_for_each_entry(info, head, list) { | 501 | + list_for_each_entry(info, head, list) { |
452 | + /* exact size iova_info */ | 502 | + /* exact size iova_info */ |
453 | + if (!info->in_use && info->size == size) { | 503 | + if (!info->in_use && info->size == size) { |
454 | + info->in_use = true; | 504 | + info->in_use = true; |
455 | + va = info->start_iova; | 505 | + va = info->start_iova; |
456 | + *curr_iova_size -= size; | 506 | + *curr_iova_size -= size; |
457 | + found = true; | ||
458 | + dev_dbg(uaudio_qdev->dev, "exact size: %zu found\n", size); | ||
459 | + goto done; | 507 | + goto done; |
460 | + } else if (!info->in_use && tmp_size >= info->size) { | 508 | + } else if (!info->in_use && tmp_size >= info->size) { |
461 | + if (!new_info) | 509 | + if (!new_info) |
462 | + new_info = info; | 510 | + new_info = info; |
463 | + dev_dbg(uaudio_qdev->dev, "partial size: %zu found\n", | ||
464 | + info->size); | ||
465 | + tmp_size -= info->size; | 511 | + tmp_size -= info->size; |
466 | + if (tmp_size) | 512 | + if (tmp_size) |
467 | + continue; | 513 | + continue; |
468 | + | 514 | + |
469 | + va = new_info->start_iova; | 515 | + va = new_info->start_iova; |
... | ... | ||
473 | + iova_info, list); | 519 | + iova_info, list); |
474 | + new_info->in_use = true; | 520 | + new_info->in_use = true; |
475 | + } | 521 | + } |
476 | + info->in_use = true; | 522 | + info->in_use = true; |
477 | + *curr_iova_size -= size; | 523 | + *curr_iova_size -= size; |
478 | + found = true; | ||
479 | + goto done; | 524 | + goto done; |
480 | + } else { | 525 | + } else { |
481 | + /* iova region in use */ | 526 | + /* iova region in use */ |
482 | + new_info = NULL; | 527 | + new_info = NULL; |
483 | + tmp_size = size; | 528 | + tmp_size = size; |
484 | + } | 529 | + } |
485 | + } | 530 | + } |
486 | + | 531 | + |
487 | + info = kzalloc(sizeof(struct iova_info), GFP_KERNEL); | 532 | + info = kzalloc(sizeof(*info), GFP_KERNEL); |
488 | + if (!info) { | 533 | + if (!info) { |
489 | + va = 0; | 534 | + va = 0; |
490 | + goto done; | 535 | + goto done; |
491 | + } | 536 | + } |
492 | + | 537 | + |
493 | + va = info->start_iova = *curr_iova; | 538 | + va = *curr_iova; |
539 | + info->start_iova = *curr_iova; | ||
494 | + info->size = size; | 540 | + info->size = size; |
495 | + info->in_use = true; | 541 | + info->in_use = true; |
496 | + *curr_iova += size; | 542 | + *curr_iova += size; |
497 | + *curr_iova_size -= size; | 543 | + *curr_iova_size -= size; |
498 | + found = true; | ||
499 | + list_add_tail(&info->list, head); | 544 | + list_add_tail(&info->list, head); |
500 | + | 545 | + |
501 | +done: | 546 | +done: |
502 | + if (!found) | ||
503 | + dev_err(uaudio_qdev->dev, "unable to find %zu size iova\n", | ||
504 | + size); | ||
505 | + else | ||
506 | + dev_dbg(uaudio_qdev->dev, | ||
507 | + "va:0x%08lx curr_iova:0x%08lx curr_iova_size:%zu\n", | ||
508 | + va, *curr_iova, *curr_iova_size); | ||
509 | + | ||
510 | + return va; | 547 | + return va; |
511 | +} | 548 | +} |
512 | + | 549 | + |
513 | +static void uaudio_put_iova(unsigned long va, size_t size, struct list_head | 550 | +static void uaudio_put_iova(unsigned long va, size_t size, struct list_head |
514 | + *head, size_t *curr_iova_size) | 551 | + *head, size_t *curr_iova_size) |
... | ... | ||
517 | + size_t tmp_size = size; | 554 | + size_t tmp_size = size; |
518 | + bool found = false; | 555 | + bool found = false; |
519 | + | 556 | + |
520 | + list_for_each_entry(info, head, list) { | 557 | + list_for_each_entry(info, head, list) { |
521 | + if (info->start_iova == va) { | 558 | + if (info->start_iova == va) { |
522 | + if (!info->in_use) { | 559 | + if (!info->in_use) |
523 | + dev_err(uaudio_qdev->dev, "va %lu is not in use\n", | ||
524 | + va); | ||
525 | + return; | 560 | + return; |
526 | + } | 561 | + |
527 | + found = true; | 562 | + found = true; |
528 | + info->in_use = false; | 563 | + info->in_use = false; |
529 | + if (info->size == size) | 564 | + if (info->size == size) |
530 | + goto done; | 565 | + goto done; |
531 | + } | 566 | + } |
... | ... | ||
536 | + if (!tmp_size) | 571 | + if (!tmp_size) |
537 | + goto done; | 572 | + goto done; |
538 | + } | 573 | + } |
539 | + } | 574 | + } |
540 | + | 575 | + |
541 | + if (!found) { | 576 | + if (!found) |
542 | + dev_err(uaudio_qdev->dev, "unable to find the va %lu\n", va); | ||
543 | + return; | 577 | + return; |
544 | + } | 578 | + |
545 | +done: | 579 | +done: |
546 | + *curr_iova_size += size; | 580 | + *curr_iova_size += size; |
547 | + dev_dbg(uaudio_qdev->dev, "curr_iova_size %zu\n", *curr_iova_size); | ||
548 | +} | 581 | +} |
549 | + | 582 | + |
550 | +/** | 583 | +/** |
551 | + * uaudio_iommu_unmap() - unmaps iommu memory for adsp | 584 | + * uaudio_iommu_unmap() - unmaps iommu memory for adsp |
552 | + * @mtype: ring type | 585 | + * @mtype: ring type |
... | ... | ||
556 | + * | 589 | + * |
557 | + * Unmaps the memory region that was previously assigned to the adsp. | 590 | + * Unmaps the memory region that was previously assigned to the adsp. |
558 | + * | 591 | + * |
559 | + */ | 592 | + */ |
560 | +static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, | 593 | +static void uaudio_iommu_unmap(enum mem_type mtype, unsigned long va, |
561 | + size_t iova_size, size_t mapped_iova_size) | 594 | + size_t iova_size, size_t mapped_iova_size) |
562 | +{ | 595 | +{ |
563 | + size_t umap_size; | 596 | + size_t umap_size; |
564 | + bool unmap = true; | 597 | + bool unmap = true; |
565 | + | 598 | + |
566 | + if (!va || !iova_size) | 599 | + if (!va || !iova_size) |
... | ... | ||
574 | + unmap = false; | 607 | + unmap = false; |
575 | + break; | 608 | + break; |
576 | + | 609 | + |
577 | + case MEM_XFER_RING: | 610 | + case MEM_XFER_RING: |
578 | + uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_ring_list, | 611 | + uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_ring_list, |
579 | + &uaudio_qdev->xfer_ring_iova_size); | 612 | + &uaudio_qdev->xfer_ring_iova_size); |
580 | + break; | 613 | + break; |
581 | + case MEM_XFER_BUF: | 614 | + case MEM_XFER_BUF: |
582 | + uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_buf_list, | 615 | + uaudio_put_iova(va, iova_size, &uaudio_qdev->xfer_buf_list, |
583 | + &uaudio_qdev->xfer_buf_iova_size); | 616 | + &uaudio_qdev->xfer_buf_iova_size); |
584 | + break; | 617 | + break; |
585 | + default: | 618 | + default: |
586 | + dev_err(uaudio_qdev->dev, "unknown mem type %d\n", mtype); | ||
587 | + unmap = false; | 619 | + unmap = false; |
588 | + } | 620 | + } |
589 | + | 621 | + |
590 | + if (!unmap || !mapped_iova_size) | 622 | + if (!unmap || !mapped_iova_size) |
591 | + return; | 623 | + return; |
592 | + | 624 | + |
593 | + dev_dbg(uaudio_qdev->dev, "type %d: unmap iova 0x%08lx size %zu\n", | 625 | + umap_size = iommu_unmap(uaudio_qdev->data->domain, va, mapped_iova_size); |
594 | + mtype, va, mapped_iova_size); | ||
595 | + | ||
596 | + umap_size = iommu_unmap(uaudio_qdev->domain, va, mapped_iova_size); | ||
597 | + if (umap_size != mapped_iova_size) | 626 | + if (umap_size != mapped_iova_size) |
598 | + dev_err(uaudio_qdev->dev, | 627 | + dev_err(uaudio_qdev->data->dev, |
599 | + "unmapped size %zu for iova 0x%08lx of mapped size %zu\n", | 628 | + "unmapped size %zu for iova 0x%08lx of mapped size %zu\n", |
600 | + umap_size, va, mapped_iova_size); | 629 | + umap_size, va, mapped_iova_size); |
601 | +} | 630 | +} |
602 | + | 631 | + |
603 | +/** | 632 | +/** |
... | ... | ||
612 | + * used by the adsp. This will be mapped to the domain, which is created by | 641 | + * used by the adsp. This will be mapped to the domain, which is created by |
613 | + * the ASoC USB backend driver. | 642 | + * the ASoC USB backend driver. |
614 | + * | 643 | + * |
615 | + */ | 644 | + */ |
616 | +static unsigned long uaudio_iommu_map(enum mem_type mtype, bool dma_coherent, | 645 | +static unsigned long uaudio_iommu_map(enum mem_type mtype, bool dma_coherent, |
617 | + phys_addr_t pa, size_t size, struct sg_table *sgt) | 646 | + phys_addr_t pa, size_t size, |
618 | +{ | 647 | + struct sg_table *sgt) |
619 | + unsigned long va_sg, va = 0; | 648 | +{ |
649 | + struct scatterlist *sg; | ||
650 | + unsigned long va = 0; | ||
651 | + size_t total_len = 0; | ||
652 | + unsigned long va_sg; | ||
653 | + phys_addr_t pa_sg; | ||
620 | + bool map = true; | 654 | + bool map = true; |
621 | + int i, ret; | 655 | + size_t sg_len; |
622 | + size_t sg_len, total_len = 0; | 656 | + int prot; |
623 | + struct scatterlist *sg; | 657 | + int ret; |
624 | + phys_addr_t pa_sg; | 658 | + int i; |
625 | + int prot = IOMMU_READ | IOMMU_WRITE; | 659 | + |
660 | + prot = IOMMU_READ | IOMMU_WRITE; | ||
626 | + | 661 | + |
627 | + if (dma_coherent) | 662 | + if (dma_coherent) |
628 | + prot |= IOMMU_CACHE; | 663 | + prot |= IOMMU_CACHE; |
629 | + | 664 | + |
630 | + switch (mtype) { | 665 | + switch (mtype) { |
... | ... | ||
634 | + if (uaudio_qdev->er_mapped) | 669 | + if (uaudio_qdev->er_mapped) |
635 | + map = false; | 670 | + map = false; |
636 | + break; | 671 | + break; |
637 | + case MEM_XFER_RING: | 672 | + case MEM_XFER_RING: |
638 | + va = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova, | 673 | + va = uaudio_get_iova(&uaudio_qdev->curr_xfer_ring_iova, |
639 | + &uaudio_qdev->xfer_ring_iova_size, &uaudio_qdev->xfer_ring_list, | 674 | + &uaudio_qdev->xfer_ring_iova_size, |
640 | + size); | 675 | + &uaudio_qdev->xfer_ring_list, size); |
641 | + break; | 676 | + break; |
642 | + case MEM_XFER_BUF: | 677 | + case MEM_XFER_BUF: |
643 | + va = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova, | 678 | + va = uaudio_get_iova(&uaudio_qdev->curr_xfer_buf_iova, |
644 | + &uaudio_qdev->xfer_buf_iova_size, &uaudio_qdev->xfer_buf_list, | 679 | + &uaudio_qdev->xfer_buf_iova_size, |
645 | + size); | 680 | + &uaudio_qdev->xfer_buf_list, size); |
646 | + break; | 681 | + break; |
647 | + default: | 682 | + default: |
648 | + dev_err(uaudio_qdev->dev, "unknown mem type %d\n", mtype); | 683 | + dev_err(uaudio_qdev->data->dev, "unknown mem type %d\n", mtype); |
649 | + } | 684 | + } |
650 | + | 685 | + |
651 | + if (!va || !map) | 686 | + if (!va || !map) |
652 | + goto done; | 687 | + goto done; |
653 | + | 688 | + |
... | ... | ||
656 | + | 691 | + |
657 | + va_sg = va; | 692 | + va_sg = va; |
658 | + for_each_sg(sgt->sgl, sg, sgt->nents, i) { | 693 | + for_each_sg(sgt->sgl, sg, sgt->nents, i) { |
659 | + sg_len = PAGE_ALIGN(sg->offset + sg->length); | 694 | + sg_len = PAGE_ALIGN(sg->offset + sg->length); |
660 | + pa_sg = page_to_phys(sg_page(sg)); | 695 | + pa_sg = page_to_phys(sg_page(sg)); |
661 | + ret = iommu_map(uaudio_qdev->domain, va_sg, pa_sg, sg_len, | 696 | + ret = iommu_map(uaudio_qdev->data->domain, va_sg, pa_sg, sg_len, |
662 | + prot, GFP_KERNEL); | 697 | + prot, GFP_KERNEL); |
663 | + if (ret) { | 698 | + if (ret) { |
664 | + dev_err(uaudio_qdev->dev, "mapping failed ret%d\n", ret); | ||
665 | + dev_err(uaudio_qdev->dev, | ||
666 | + "type:%d, pa:%pa iova:0x%08lx sg_len:%zu\n", | ||
667 | + mtype, &pa_sg, va_sg, sg_len); | ||
668 | + uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); | 699 | + uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); |
669 | + va = 0; | 700 | + va = 0; |
670 | + goto done; | 701 | + goto done; |
671 | + } | 702 | + } |
672 | + dev_dbg(uaudio_qdev->dev, | 703 | + |
673 | + "type:%d map pa:%pa to iova:0x%08lx len:%zu offset:%u\n", | ||
674 | + mtype, &pa_sg, va_sg, sg_len, sg->offset); | ||
675 | + va_sg += sg_len; | 704 | + va_sg += sg_len; |
676 | + total_len += sg_len; | 705 | + total_len += sg_len; |
677 | + } | 706 | + } |
678 | + | 707 | + |
679 | + if (size != total_len) { | 708 | + if (size != total_len) { |
680 | + dev_err(uaudio_qdev->dev, "iova size %zu != mapped iova size %zu\n", | ||
681 | + size, total_len); | ||
682 | + uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); | 709 | + uaudio_iommu_unmap(MEM_XFER_BUF, va, size, total_len); |
683 | + va = 0; | 710 | + va = 0; |
684 | + } | 711 | + } |
685 | + return va; | 712 | + return va; |
686 | + | 713 | + |
687 | +skip_sgt_map: | 714 | +skip_sgt_map: |
688 | + dev_dbg(uaudio_qdev->dev, "type:%d map pa:%pa to iova:0x%08lx size:%zu\n", | 715 | + iommu_map(uaudio_qdev->data->domain, va, pa, size, prot, GFP_KERNEL); |
689 | + mtype, &pa, va, size); | 716 | + |
690 | + | ||
691 | + ret = iommu_map(uaudio_qdev->domain, va, pa, size, prot, GFP_KERNEL); | ||
692 | + if (ret) | ||
693 | + dev_err(uaudio_qdev->dev, | ||
694 | + "failed to map pa:%pa iova:0x%lx type:%d ret:%d\n", | ||
695 | + &pa, va, mtype, ret); | ||
696 | +done: | 717 | +done: |
697 | + return va; | 718 | + return va; |
698 | +} | 719 | +} |
699 | + | 720 | + |
700 | +/* looks up alias, if any, for controller DT node and returns the index */ | 721 | +/* looks up alias, if any, for controller DT node and returns the index */ |
... | ... | ||
714 | + * Cleans up the transfer ring related resources which are assigned per | 735 | + * Cleans up the transfer ring related resources which are assigned per |
715 | + * endpoint from XHCI. This is invoked when the USB endpoints are no | 736 | + * endpoint from XHCI. This is invoked when the USB endpoints are no |
716 | + * longer in use by the adsp. | 737 | + * longer in use by the adsp. |
717 | + * | 738 | + * |
718 | + */ | 739 | + */ |
719 | +static void uaudio_dev_intf_cleanup(struct usb_device *udev, | 740 | +static void uaudio_dev_intf_cleanup(struct usb_device *udev, struct intf_info *info) |
720 | + struct intf_info *info) | ||
721 | +{ | 741 | +{ |
722 | + uaudio_iommu_unmap(MEM_XFER_RING, info->data_xfer_ring_va, | 742 | + uaudio_iommu_unmap(MEM_XFER_RING, info->data_xfer_ring_va, |
723 | + info->data_xfer_ring_size, info->data_xfer_ring_size); | 743 | + info->data_xfer_ring_size, info->data_xfer_ring_size); |
724 | + info->data_xfer_ring_va = 0; | 744 | + info->data_xfer_ring_va = 0; |
725 | + info->data_xfer_ring_size = 0; | 745 | + info->data_xfer_ring_size = 0; |
726 | + | 746 | + |
727 | + uaudio_iommu_unmap(MEM_XFER_RING, info->sync_xfer_ring_va, | 747 | + uaudio_iommu_unmap(MEM_XFER_RING, info->sync_xfer_ring_va, |
728 | + info->sync_xfer_ring_size, info->sync_xfer_ring_size); | 748 | + info->sync_xfer_ring_size, info->sync_xfer_ring_size); |
729 | + info->sync_xfer_ring_va = 0; | 749 | + info->sync_xfer_ring_va = 0; |
730 | + info->sync_xfer_ring_size = 0; | 750 | + info->sync_xfer_ring_size = 0; |
731 | + | 751 | + |
732 | + uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_va, | 752 | + uaudio_iommu_unmap(MEM_XFER_BUF, info->xfer_buf_va, info->xfer_buf_size, |
733 | + info->xfer_buf_size, info->xfer_buf_size); | 753 | + info->xfer_buf_size); |
734 | + info->xfer_buf_va = 0; | 754 | + info->xfer_buf_va = 0; |
735 | + | 755 | + |
736 | + usb_free_coherent(udev, info->xfer_buf_size, | 756 | + usb_free_coherent(udev, info->xfer_buf_size, info->xfer_buf, |
737 | + info->xfer_buf, info->xfer_buf_pa); | 757 | + info->xfer_buf_pa); |
738 | + info->xfer_buf_size = 0; | 758 | + info->xfer_buf_size = 0; |
739 | + info->xfer_buf = NULL; | 759 | + info->xfer_buf = NULL; |
740 | + info->xfer_buf_pa = 0; | 760 | + info->xfer_buf_pa = 0; |
741 | + | 761 | + |
742 | + info->in_use = false; | 762 | + info->in_use = false; |
... | ... | ||
755 | +{ | 775 | +{ |
756 | + clear_bit(dev->chip->card->number, &uaudio_qdev->card_slot); | 776 | + clear_bit(dev->chip->card->number, &uaudio_qdev->card_slot); |
757 | + /* all audio devices are disconnected */ | 777 | + /* all audio devices are disconnected */ |
758 | + if (!uaudio_qdev->card_slot) { | 778 | + if (!uaudio_qdev->card_slot) { |
759 | + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, | 779 | + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, |
760 | + PAGE_SIZE); | 780 | + PAGE_SIZE); |
761 | + xhci_sideband_remove_interrupter(uadev[dev->chip->card->number].sb); | 781 | + xhci_sideband_remove_interrupter(uadev[dev->chip->card->number].sb); |
762 | + } | 782 | + } |
763 | +} | 783 | +} |
764 | + | 784 | + |
765 | +static void uaudio_dev_cleanup(struct uaudio_dev *dev) | 785 | +static void uaudio_dev_cleanup(struct uaudio_dev *dev) |
... | ... | ||
772 | + /* free xfer buffer and unmap xfer ring and buf per interface */ | 792 | + /* free xfer buffer and unmap xfer ring and buf per interface */ |
773 | + for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { | 793 | + for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { |
774 | + if (!dev->info[if_idx].in_use) | 794 | + if (!dev->info[if_idx].in_use) |
775 | + continue; | 795 | + continue; |
776 | + uaudio_dev_intf_cleanup(dev->udev, &dev->info[if_idx]); | 796 | + uaudio_dev_intf_cleanup(dev->udev, &dev->info[if_idx]); |
777 | + dev_dbg(uaudio_qdev->dev, "release resources: intf# %d card# %d\n", | 797 | + dev_dbg(uaudio_qdev->data->dev, |
798 | + "release resources: intf# %d card# %d\n", | ||
778 | + dev->info[if_idx].intf_num, dev->chip->card->number); | 799 | + dev->info[if_idx].intf_num, dev->chip->card->number); |
779 | + } | 800 | + } |
780 | + | 801 | + |
781 | + dev->num_intf = 0; | 802 | + dev->num_intf = 0; |
782 | + | 803 | + |
... | ... | ||
802 | + snd_usb_hw_free(subs); | 823 | + snd_usb_hw_free(subs); |
803 | + snd_usb_autosuspend(chip); | 824 | + snd_usb_autosuspend(chip); |
804 | +} | 825 | +} |
805 | + | 826 | + |
806 | +/* QMI service disconnect handlers */ | 827 | +/* QMI service disconnect handlers */ |
807 | +static void qmi_disconnect_work(struct work_struct *w) | 828 | +static void qmi_stop_session(void) |
808 | +{ | 829 | +{ |
830 | + struct snd_usb_substream *subs; | ||
831 | + struct usb_host_endpoint *ep; | ||
832 | + struct snd_usb_audio *chip; | ||
809 | + struct intf_info *info; | 833 | + struct intf_info *info; |
810 | + int idx, if_idx; | 834 | + int pcm_card_num; |
811 | + struct snd_usb_substream *subs; | 835 | + int if_idx; |
812 | + struct snd_usb_audio *chip; | 836 | + int idx; |
813 | + | 837 | + |
814 | + mutex_lock(&qdev_mutex); | 838 | + mutex_lock(&qdev_mutex); |
815 | + /* find all active intf for set alt 0 and cleanup usb audio dev */ | 839 | + /* find all active intf for set alt 0 and cleanup usb audio dev */ |
816 | + for (idx = 0; idx < SNDRV_CARDS; idx++) { | 840 | + for (idx = 0; idx < SNDRV_CARDS; idx++) { |
817 | + if (!atomic_read(&uadev[idx].in_use)) | 841 | + if (!atomic_read(&uadev[idx].in_use)) |
... | ... | ||
820 | + chip = uadev[idx].chip; | 844 | + chip = uadev[idx].chip; |
821 | + for (if_idx = 0; if_idx < uadev[idx].num_intf; if_idx++) { | 845 | + for (if_idx = 0; if_idx < uadev[idx].num_intf; if_idx++) { |
822 | + if (!uadev[idx].info || !uadev[idx].info[if_idx].in_use) | 846 | + if (!uadev[idx].info || !uadev[idx].info[if_idx].in_use) |
823 | + continue; | 847 | + continue; |
824 | + info = &uadev[idx].info[if_idx]; | 848 | + info = &uadev[idx].info[if_idx]; |
825 | + subs = find_substream(info->pcm_card_num, | 849 | + pcm_card_num = info->pcm_card_num; |
826 | + info->pcm_dev_num, | 850 | + subs = find_substream(pcm_card_num, info->pcm_dev_num, |
827 | + info->direction); | 851 | + info->direction); |
828 | + if (!subs || !chip || atomic_read(&chip->shutdown)) { | 852 | + if (!subs || !chip || atomic_read(&chip->shutdown)) { |
829 | + dev_err(&subs->dev->dev, | 853 | + dev_err(&subs->dev->dev, |
830 | + "no sub for c#%u dev#%u dir%u\n", | 854 | + "no sub for c#%u dev#%u dir%u\n", |
831 | + info->pcm_card_num, | 855 | + info->pcm_card_num, |
832 | + info->pcm_dev_num, | 856 | + info->pcm_dev_num, |
833 | + info->direction); | 857 | + info->direction); |
834 | + continue; | 858 | + continue; |
835 | + } | 859 | + } |
860 | + /* Release XHCI endpoints */ | ||
861 | + if (info->data_ep_pipe) | ||
862 | + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, | ||
863 | + info->data_ep_pipe); | ||
864 | + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep); | ||
865 | + | ||
866 | + if (info->sync_ep_pipe) | ||
867 | + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, | ||
868 | + info->sync_ep_pipe); | ||
869 | + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep); | ||
870 | + | ||
836 | + disable_audio_stream(subs); | 871 | + disable_audio_stream(subs); |
837 | + } | 872 | + } |
838 | + atomic_set(&uadev[idx].in_use, 0); | 873 | + atomic_set(&uadev[idx].in_use, 0); |
839 | + mutex_lock(&chip->mutex); | 874 | + mutex_lock(&chip->mutex); |
840 | + uaudio_dev_cleanup(&uadev[idx]); | 875 | + uaudio_dev_cleanup(&uadev[idx]); |
841 | + mutex_unlock(&chip->mutex); | 876 | + mutex_unlock(&chip->mutex); |
842 | + } | 877 | + } |
843 | + mutex_unlock(&qdev_mutex); | 878 | + mutex_unlock(&qdev_mutex); |
844 | +} | 879 | +} |
845 | + | 880 | + |
846 | +/** | 881 | +/** |
882 | + * uaudio_sideband_notifier() - xHCI sideband event handler | ||
883 | + * @intf: USB interface handle | ||
884 | + * @evt: xHCI sideband event type | ||
885 | + * | ||
886 | + * This callback is executed when the xHCI sideband encounters a sequence | ||
887 | + * that requires the sideband clients to take action. An example, is when | ||
888 | + * xHCI frees the transfer ring, so the client has to ensure that the | ||
889 | + * offload path is halted. | ||
890 | + * | ||
891 | + */ | ||
892 | +static int uaudio_sideband_notifier(struct usb_interface *intf, | ||
893 | + struct xhci_sideband_event *evt) | ||
894 | +{ | ||
895 | + struct snd_usb_audio *chip; | ||
896 | + struct uaudio_dev *dev; | ||
897 | + int if_idx; | ||
898 | + | ||
899 | + if (!intf || !evt) | ||
900 | + return 0; | ||
901 | + | ||
902 | + chip = usb_get_intfdata(intf); | ||
903 | + | ||
904 | + mutex_lock(&qdev_mutex); | ||
905 | + mutex_lock(&chip->mutex); | ||
906 | + | ||
907 | + dev = &uadev[chip->card->number]; | ||
908 | + | ||
909 | + if (evt->type == XHCI_SIDEBAND_XFER_RING_FREE) { | ||
910 | + unsigned int *ep = (unsigned int *) evt->evt_data; | ||
911 | + | ||
912 | + for (if_idx = 0; if_idx < dev->num_intf; if_idx++) { | ||
913 | + if (dev->info[if_idx].data_ep_idx == *ep || | ||
914 | + dev->info[if_idx].sync_ep_idx == *ep) | ||
915 | + uaudio_send_disconnect_ind(chip); | ||
916 | + } | ||
917 | + } | ||
918 | + | ||
919 | + mutex_unlock(&qdev_mutex); | ||
920 | + mutex_unlock(&chip->mutex); | ||
921 | + | ||
922 | + return 0; | ||
923 | +} | ||
924 | + | ||
925 | +/** | ||
847 | + * qmi_bye_cb() - qmi bye message callback | 926 | + * qmi_bye_cb() - qmi bye message callback |
848 | + * @handle: QMI handle | 927 | + * @handle: QMI handle |
849 | + * @node: id of the dying node | 928 | + * @node: id of the dying node |
850 | + * | 929 | + * |
851 | + * This callback is invoked when the QMI bye control message is received | 930 | + * This callback is invoked when the QMI bye control message is received |
... | ... | ||
860 | + | 939 | + |
861 | + if (svc->uaudio_svc_hdl != handle) | 940 | + if (svc->uaudio_svc_hdl != handle) |
862 | + return; | 941 | + return; |
863 | + | 942 | + |
864 | + if (svc->client_connected && svc->client_sq.sq_node == node) { | 943 | + if (svc->client_connected && svc->client_sq.sq_node == node) { |
865 | + queue_work(svc->uaudio_wq, &svc->qmi_disconnect_work); | 944 | + qmi_stop_session(); |
945 | + | ||
946 | + /* clear QMI client parameters to block further QMI messages */ | ||
866 | + svc->client_sq.sq_node = 0; | 947 | + svc->client_sq.sq_node = 0; |
867 | + svc->client_sq.sq_port = 0; | 948 | + svc->client_sq.sq_port = 0; |
868 | + svc->client_sq.sq_family = 0; | 949 | + svc->client_sq.sq_family = 0; |
869 | + svc->client_connected = false; | 950 | + svc->client_connected = false; |
870 | + } | 951 | + } |
... | ... | ||
884 | +static void qmi_svc_disconnect_cb(struct qmi_handle *handle, | 965 | +static void qmi_svc_disconnect_cb(struct qmi_handle *handle, |
885 | + unsigned int node, unsigned int port) | 966 | + unsigned int node, unsigned int port) |
886 | +{ | 967 | +{ |
887 | + struct uaudio_qmi_svc *svc; | 968 | + struct uaudio_qmi_svc *svc; |
888 | + | 969 | + |
889 | + if (uaudio_svc == NULL) | 970 | + if (!uaudio_svc) |
890 | + return; | 971 | + return; |
891 | + | 972 | + |
892 | + svc = uaudio_svc; | 973 | + svc = uaudio_svc; |
893 | + if (svc->uaudio_svc_hdl != handle) | 974 | + if (svc->uaudio_svc_hdl != handle) |
894 | + return; | 975 | + return; |
895 | + | 976 | + |
896 | + if (svc->client_connected && svc->client_sq.sq_node == node && | 977 | + if (svc->client_connected && svc->client_sq.sq_node == node && |
897 | + svc->client_sq.sq_port == port) { | 978 | + svc->client_sq.sq_port == port) { |
898 | + queue_work(svc->uaudio_wq, &svc->qmi_disconnect_work); | 979 | + qmi_stop_session(); |
980 | + | ||
981 | + /* clear QMI client parameters to block further QMI messages */ | ||
899 | + svc->client_sq.sq_node = 0; | 982 | + svc->client_sq.sq_node = 0; |
900 | + svc->client_sq.sq_port = 0; | 983 | + svc->client_sq.sq_port = 0; |
901 | + svc->client_sq.sq_family = 0; | 984 | + svc->client_sq.sq_family = 0; |
902 | + svc->client_connected = false; | 985 | + svc->client_connected = false; |
903 | + } | 986 | + } |
... | ... | ||
932 | + * until the audio stream is stopped. Will issue the USB set interface control | 1015 | + * until the audio stream is stopped. Will issue the USB set interface control |
933 | + * message to enable the data interface. | 1016 | + * message to enable the data interface. |
934 | + * | 1017 | + * |
935 | + */ | 1018 | + */ |
936 | +static int enable_audio_stream(struct snd_usb_substream *subs, | 1019 | +static int enable_audio_stream(struct snd_usb_substream *subs, |
937 | + snd_pcm_format_t pcm_format, | 1020 | + snd_pcm_format_t pcm_format, |
938 | + unsigned int channels, unsigned int cur_rate, | 1021 | + unsigned int channels, unsigned int cur_rate, |
939 | + int datainterval) | 1022 | + int datainterval) |
940 | +{ | 1023 | +{ |
941 | + struct snd_usb_audio *chip = subs->stream->chip; | ||
942 | + struct snd_pcm_hw_params params; | 1024 | + struct snd_pcm_hw_params params; |
1025 | + struct snd_usb_audio *chip; | ||
1026 | + struct snd_interval *i; | ||
943 | + struct snd_mask *m; | 1027 | + struct snd_mask *m; |
944 | + struct snd_interval *i; | ||
945 | + int ret; | 1028 | + int ret; |
1029 | + | ||
1030 | + chip = subs->stream->chip; | ||
946 | + | 1031 | + |
947 | + _snd_pcm_hw_params_any(¶ms); | 1032 | + _snd_pcm_hw_params_any(¶ms); |
948 | + | 1033 | + |
949 | + m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT); | 1034 | + m = hw_param_mask(¶ms, SNDRV_PCM_HW_PARAM_FORMAT); |
950 | + snd_mask_leave(m, pcm_format); | 1035 | + snd_mask_leave(m, pcm_format); |
951 | + | 1036 | + |
952 | + i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS); | 1037 | + i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_CHANNELS); |
953 | + snd_interval_setinteger(i); | 1038 | + snd_interval_setinteger(i); |
954 | + i->min = i->max = channels; | 1039 | + i->min = channels; |
1040 | + i->max = channels; | ||
955 | + | 1041 | + |
956 | + i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE); | 1042 | + i = hw_param_interval(¶ms, SNDRV_PCM_HW_PARAM_RATE); |
957 | + snd_interval_setinteger(i); | 1043 | + snd_interval_setinteger(i); |
958 | + i->min = i->max = cur_rate; | 1044 | + i->min = cur_rate; |
1045 | + i->max = cur_rate; | ||
959 | + | 1046 | + |
960 | + pm_runtime_barrier(&chip->intf[0]->dev); | 1047 | + pm_runtime_barrier(&chip->intf[0]->dev); |
961 | + snd_usb_autoresume(chip); | 1048 | + snd_usb_autoresume(chip); |
962 | + | 1049 | + |
963 | + ret = snd_usb_hw_params(subs, ¶ms); | 1050 | + ret = snd_usb_hw_params(subs, ¶ms); |
... | ... | ||
979 | + if (ret < 0) | 1066 | + if (ret < 0) |
980 | + goto unlock; | 1067 | + goto unlock; |
981 | + | 1068 | + |
982 | + snd_usb_unlock_shutdown(chip); | 1069 | + snd_usb_unlock_shutdown(chip); |
983 | + | 1070 | + |
984 | + dev_dbg(uaudio_qdev->dev, | 1071 | + dev_dbg(uaudio_qdev->data->dev, |
985 | + "selected %s iface:%d altsetting:%d datainterval:%dus\n", | 1072 | + "selected %s iface:%d altsetting:%d datainterval:%dus\n", |
986 | + subs->direction ? "capture" : "playback", | 1073 | + subs->direction ? "capture" : "playback", |
987 | + subs->cur_audiofmt->iface, subs->cur_audiofmt->altsetting, | 1074 | + subs->cur_audiofmt->iface, subs->cur_audiofmt->altsetting, |
988 | + (1 << subs->cur_audiofmt->datainterval) * | 1075 | + (1 << subs->cur_audiofmt->datainterval) * |
989 | + (subs->dev->speed >= USB_SPEED_HIGH ? | 1076 | + (subs->dev->speed >= USB_SPEED_HIGH ? |
... | ... | ||
1013 | + | 1100 | + |
1014 | + return NULL; | 1101 | + return NULL; |
1015 | +} | 1102 | +} |
1016 | + | 1103 | + |
1017 | +/** | 1104 | +/** |
1018 | + * prepare_qmi_response() - prepare stream enable response | 1105 | + * uaudio_transfer_buffer_setup() - fetch and populate xfer buffer params |
1019 | + * @subs: usb substream | 1106 | + * @subs: usb substream |
1020 | + * @req_msg: QMI request message | 1107 | + * @xfer_buf: xfer buf to be allocated |
1108 | + * @xfer_buf_len: size of allocation | ||
1109 | + * @mem_info: QMI response info | ||
1110 | + * | ||
1111 | + * Allocates and maps the transfer buffers that will be utilized by the | ||
1112 | + * audio DSP. Will populate the information in the QMI response that is | ||
1113 | + * sent back to the stream enable request. | ||
1114 | + * | ||
1115 | + */ | ||
1116 | +static int uaudio_transfer_buffer_setup(struct snd_usb_substream *subs, | ||
1117 | + u8 *xfer_buf, u32 xfer_buf_len, | ||
1118 | + struct mem_info_v01 *mem_info) | ||
1119 | +{ | ||
1120 | + struct sg_table xfer_buf_sgt; | ||
1121 | + phys_addr_t xfer_buf_pa; | ||
1122 | + u32 len = xfer_buf_len; | ||
1123 | + bool dma_coherent; | ||
1124 | + unsigned long va; | ||
1125 | + u32 remainder; | ||
1126 | + u32 mult; | ||
1127 | + int ret; | ||
1128 | + | ||
1129 | + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); | ||
1130 | + | ||
1131 | + /* xfer buffer, multiple of 4K only */ | ||
1132 | + if (!len) | ||
1133 | + len = PAGE_SIZE; | ||
1134 | + | ||
1135 | + mult = len / PAGE_SIZE; | ||
1136 | + remainder = len % PAGE_SIZE; | ||
1137 | + len = mult * PAGE_SIZE; | ||
1138 | + len += remainder ? PAGE_SIZE : 0; | ||
1139 | + | ||
1140 | + if (len > MAX_XFER_BUFF_LEN) { | ||
1141 | + dev_err(uaudio_qdev->data->dev, | ||
1142 | + "req buf len %d > max buf len %lu, setting %lu\n", | ||
1143 | + len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN); | ||
1144 | + len = MAX_XFER_BUFF_LEN; | ||
1145 | + } | ||
1146 | + | ||
1147 | + xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_pa); | ||
1148 | + if (!xfer_buf) | ||
1149 | + return -ENOMEM; | ||
1150 | + | ||
1151 | + dma_get_sgtable(subs->dev->bus->sysdev, &xfer_buf_sgt, xfer_buf, | ||
1152 | + xfer_buf_pa, len); | ||
1153 | + va = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent, xfer_buf_pa, len, | ||
1154 | + &xfer_buf_sgt); | ||
1155 | + if (!va) { | ||
1156 | + ret = -ENOMEM; | ||
1157 | + goto unmap_sync; | ||
1158 | + } | ||
1159 | + | ||
1160 | + mem_info->pa = xfer_buf_pa; | ||
1161 | + mem_info->size = len; | ||
1162 | + mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); | ||
1163 | + sg_free_table(&xfer_buf_sgt); | ||
1164 | + | ||
1165 | + return 0; | ||
1166 | + | ||
1167 | +unmap_sync: | ||
1168 | + usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_pa); | ||
1169 | + | ||
1170 | + return ret; | ||
1171 | +} | ||
1172 | + | ||
1173 | +/** | ||
1174 | + * uaudio_endpoint_setup() - fetch and populate endpoint params | ||
1175 | + * @subs: usb substream | ||
1176 | + * @endpoint: usb endpoint to add | ||
1177 | + * @card_num: uadev index | ||
1178 | + * @mem_info: QMI response info | ||
1179 | + * @ep_desc: QMI ep desc response field | ||
1180 | + * | ||
1181 | + * Initialize the USB endpoint being used for a particular USB | ||
1182 | + * stream. Will request XHCI sec intr to reserve the EP for | ||
1183 | + * offloading as well as populating the QMI response with the | ||
1184 | + * transfer ring parameters. | ||
1185 | + * | ||
1186 | + */ | ||
1187 | +static phys_addr_t | ||
1188 | +uaudio_endpoint_setup(struct snd_usb_substream *subs, | ||
1189 | + struct snd_usb_endpoint *endpoint, int card_num, | ||
1190 | + struct mem_info_v01 *mem_info, | ||
1191 | + struct usb_endpoint_descriptor_v01 *ep_desc) | ||
1192 | +{ | ||
1193 | + struct usb_host_endpoint *ep; | ||
1194 | + phys_addr_t tr_pa = 0; | ||
1195 | + struct sg_table *sgt; | ||
1196 | + bool dma_coherent; | ||
1197 | + unsigned long va; | ||
1198 | + struct page *pg; | ||
1199 | + int ret = -ENODEV; | ||
1200 | + | ||
1201 | + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); | ||
1202 | + | ||
1203 | + ep = usb_pipe_endpoint(subs->dev, endpoint->pipe); | ||
1204 | + if (!ep) { | ||
1205 | + dev_err(uaudio_qdev->data->dev, "data ep # %d context is null\n", | ||
1206 | + subs->data_endpoint->ep_num); | ||
1207 | + goto exit; | ||
1208 | + } | ||
1209 | + | ||
1210 | + memcpy(ep_desc, &ep->desc, sizeof(ep->desc)); | ||
1211 | + | ||
1212 | + ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep); | ||
1213 | + if (ret < 0) { | ||
1214 | + dev_err(&subs->dev->dev, | ||
1215 | + "failed to add data ep to sec intr\n"); | ||
1216 | + ret = -ENODEV; | ||
1217 | + goto exit; | ||
1218 | + } | ||
1219 | + | ||
1220 | + sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep); | ||
1221 | + if (!sgt) { | ||
1222 | + dev_err(&subs->dev->dev, | ||
1223 | + "failed to get data ep ring address\n"); | ||
1224 | + ret = -ENODEV; | ||
1225 | + goto remove_ep; | ||
1226 | + } | ||
1227 | + | ||
1228 | + pg = sg_page(sgt->sgl); | ||
1229 | + tr_pa = page_to_phys(pg); | ||
1230 | + mem_info->pa = sg_dma_address(sgt->sgl); | ||
1231 | + sg_free_table(sgt); | ||
1232 | + | ||
1233 | + /* data transfer ring */ | ||
1234 | + va = uaudio_iommu_map(MEM_XFER_RING, dma_coherent, tr_pa, | ||
1235 | + PAGE_SIZE, NULL); | ||
1236 | + if (!va) { | ||
1237 | + ret = -ENOMEM; | ||
1238 | + goto clear_pa; | ||
1239 | + } | ||
1240 | + | ||
1241 | + mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); | ||
1242 | + mem_info->size = PAGE_SIZE; | ||
1243 | + | ||
1244 | + return 0; | ||
1245 | + | ||
1246 | +clear_pa: | ||
1247 | + mem_info->pa = 0; | ||
1248 | +remove_ep: | ||
1249 | + xhci_sideband_remove_endpoint(uadev[card_num].sb, ep); | ||
1250 | +exit: | ||
1251 | + return ret; | ||
1252 | +} | ||
1253 | + | ||
1254 | +/** | ||
1255 | + * uaudio_event_ring_setup() - fetch and populate event ring params | ||
1256 | + * @subs: usb substream | ||
1257 | + * @card_num: uadev index | ||
1258 | + * @mem_info: QMI response info | ||
1259 | + * | ||
1260 | + * Register secondary interrupter to XHCI and fetch the event buffer info | ||
1261 | + * and populate the information into the QMI response. | ||
1262 | + * | ||
1263 | + */ | ||
1264 | +static int uaudio_event_ring_setup(struct snd_usb_substream *subs, | ||
1265 | + int card_num, struct mem_info_v01 *mem_info) | ||
1266 | +{ | ||
1267 | + struct sg_table *sgt; | ||
1268 | + phys_addr_t er_pa; | ||
1269 | + bool dma_coherent; | ||
1270 | + unsigned long va; | ||
1271 | + struct page *pg; | ||
1272 | + int ret; | ||
1273 | + | ||
1274 | + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); | ||
1275 | + er_pa = 0; | ||
1276 | + | ||
1277 | + /* event ring */ | ||
1278 | + ret = xhci_sideband_create_interrupter(uadev[card_num].sb, 1, false, | ||
1279 | + 0, uaudio_qdev->data->intr_num); | ||
1280 | + if (ret < 0) { | ||
1281 | + dev_err(&subs->dev->dev, "failed to fetch interrupter\n"); | ||
1282 | + goto exit; | ||
1283 | + } | ||
1284 | + | ||
1285 | + sgt = xhci_sideband_get_event_buffer(uadev[card_num].sb); | ||
1286 | + if (!sgt) { | ||
1287 | + dev_err(&subs->dev->dev, | ||
1288 | + "failed to get event ring address\n"); | ||
1289 | + ret = -ENODEV; | ||
1290 | + goto remove_interrupter; | ||
1291 | + } | ||
1292 | + | ||
1293 | + pg = sg_page(sgt->sgl); | ||
1294 | + er_pa = page_to_phys(pg); | ||
1295 | + mem_info->pa = sg_dma_address(sgt->sgl); | ||
1296 | + sg_free_table(sgt); | ||
1297 | + | ||
1298 | + va = uaudio_iommu_map(MEM_EVENT_RING, dma_coherent, er_pa, | ||
1299 | + PAGE_SIZE, NULL); | ||
1300 | + if (!va) { | ||
1301 | + ret = -ENOMEM; | ||
1302 | + goto clear_pa; | ||
1303 | + } | ||
1304 | + | ||
1305 | + mem_info->va = PREPEND_SID_TO_IOVA(va, uaudio_qdev->data->sid); | ||
1306 | + mem_info->size = PAGE_SIZE; | ||
1307 | + | ||
1308 | + return 0; | ||
1309 | + | ||
1310 | +clear_pa: | ||
1311 | + mem_info->pa = 0; | ||
1312 | +remove_interrupter: | ||
1313 | + xhci_sideband_remove_interrupter(uadev[card_num].sb); | ||
1314 | +exit: | ||
1315 | + return ret; | ||
1316 | +} | ||
1317 | + | ||
1318 | +/** | ||
1319 | + * uaudio_populate_uac_desc() - parse UAC parameters and populate QMI resp | ||
1320 | + * @subs: usb substream | ||
1021 | + * @resp: QMI response buffer | 1321 | + * @resp: QMI response buffer |
1022 | + * @info_idx: usb interface array index | 1322 | + * |
1023 | + * | 1323 | + * Parses information specified within UAC descriptors which explain the |
1024 | + * Prepares the QMI response for a USB QMI stream enable request. Will parse | 1324 | + * sample parameters that the device expects. This information is populated |
1025 | + * out the parameters within the stream enable request, in order to match | 1325 | + * to the QMI response sent back to the audio DSP. |
1026 | + * requested audio profile to the ones exposed by the USB device connected. | 1326 | + * |
1027 | + * | 1327 | + */ |
1028 | + * In addition, will fetch the XHCI transfer resources needed for the handoff to | 1328 | +static int uaudio_populate_uac_desc(struct snd_usb_substream *subs, |
1029 | + * happen. This includes, transfer ring and buffer addresses and secondary event | 1329 | + struct qmi_uaudio_stream_resp_msg_v01 *resp) |
1030 | + * ring address. These parameters will be communicated as part of the USB QMI | 1330 | +{ |
1031 | + * stream enable response. | 1331 | + struct usb_interface_descriptor *altsd; |
1032 | + * | 1332 | + struct usb_host_interface *alts; |
1033 | + */ | ||
1034 | +static int prepare_qmi_response(struct snd_usb_substream *subs, | ||
1035 | + struct qmi_uaudio_stream_req_msg_v01 *req_msg, | ||
1036 | + struct qmi_uaudio_stream_resp_msg_v01 *resp, int info_idx) | ||
1037 | +{ | ||
1038 | + struct usb_interface *iface; | 1333 | + struct usb_interface *iface; |
1039 | + struct usb_host_interface *alts; | 1334 | + int protocol; |
1040 | + struct usb_interface_descriptor *altsd; | ||
1041 | + struct usb_interface_assoc_descriptor *assoc; | ||
1042 | + struct usb_host_endpoint *ep; | ||
1043 | + struct uac_format_type_i_continuous_descriptor *fmt; | ||
1044 | + struct uac_format_type_i_discrete_descriptor *fmt_v1; | ||
1045 | + struct uac_format_type_i_ext_descriptor *fmt_v2; | ||
1046 | + struct uac1_as_header_descriptor *as; | ||
1047 | + struct q6usb_offload *data; | ||
1048 | + int ret; | ||
1049 | + int protocol, card_num, pcm_dev_num; | ||
1050 | + void *hdr_ptr; | ||
1051 | + u8 *xfer_buf; | ||
1052 | + unsigned int data_ep_pipe = 0, sync_ep_pipe = 0; | ||
1053 | + u32 len, mult, remainder, xfer_buf_len; | ||
1054 | + unsigned long va, tr_data_va = 0, tr_sync_va = 0; | ||
1055 | + phys_addr_t xhci_pa, xfer_buf_pa, tr_data_pa = 0, tr_sync_pa = 0; | ||
1056 | + struct sg_table *sgt; | ||
1057 | + struct sg_table xfer_buf_sgt; | ||
1058 | + struct page *pg; | ||
1059 | + bool dma_coherent; | ||
1060 | + | 1335 | + |
1061 | + iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface); | 1336 | + iface = usb_ifnum_to_if(subs->dev, subs->cur_audiofmt->iface); |
1062 | + if (!iface) { | 1337 | + if (!iface) { |
1063 | + dev_err(uaudio_qdev->dev, "interface # %d does not exist\n", | 1338 | + dev_err(&subs->dev->dev, "interface # %d does not exist\n", |
1064 | + subs->cur_audiofmt->iface); | 1339 | + subs->cur_audiofmt->iface); |
1065 | + ret = -ENODEV; | 1340 | + return -ENODEV; |
1066 | + goto err; | 1341 | + } |
1067 | + } | ||
1068 | + | ||
1069 | + assoc = iface->intf_assoc; | ||
1070 | + pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; | ||
1071 | + xfer_buf_len = req_msg->xfer_buff_size; | ||
1072 | + card_num = uaudio_qdev->last_card_num; | ||
1073 | + | 1342 | + |
1074 | + alts = &iface->altsetting[subs->cur_audiofmt->altset_idx]; | 1343 | + alts = &iface->altsetting[subs->cur_audiofmt->altset_idx]; |
1075 | + altsd = get_iface_desc(alts); | 1344 | + altsd = get_iface_desc(alts); |
1076 | + protocol = altsd->bInterfaceProtocol; | 1345 | + protocol = altsd->bInterfaceProtocol; |
1077 | + | 1346 | + |
1078 | + /* get format type */ | ||
1079 | + if (protocol != UAC_VERSION_3) { | ||
1080 | + fmt = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, | ||
1081 | + UAC_FORMAT_TYPE); | ||
1082 | + if (!fmt) { | ||
1083 | + dev_err(uaudio_qdev->dev, | ||
1084 | + "%u:%d : no UAC_FORMAT_TYPE desc\n", | ||
1085 | + subs->cur_audiofmt->iface, | ||
1086 | + subs->cur_audiofmt->altset_idx); | ||
1087 | + ret = -ENODEV; | ||
1088 | + goto err; | ||
1089 | + } | ||
1090 | + } | ||
1091 | + | ||
1092 | + if (!uadev[card_num].ctrl_intf) { | ||
1093 | + dev_err(uaudio_qdev->dev, "audio ctrl intf info not cached\n"); | ||
1094 | + ret = -ENODEV; | ||
1095 | + goto err; | ||
1096 | + } | ||
1097 | + | ||
1098 | + if (protocol != UAC_VERSION_3) { | ||
1099 | + hdr_ptr = snd_usb_find_csint_desc(uadev[card_num].ctrl_intf->extra, | ||
1100 | + uadev[card_num].ctrl_intf->extralen, NULL, | ||
1101 | + UAC_HEADER); | ||
1102 | + if (!hdr_ptr) { | ||
1103 | + dev_err(uaudio_qdev->dev, "no UAC_HEADER desc\n"); | ||
1104 | + ret = -ENODEV; | ||
1105 | + goto err; | ||
1106 | + } | ||
1107 | + } | ||
1108 | + | ||
1109 | + if (protocol == UAC_VERSION_1) { | 1347 | + if (protocol == UAC_VERSION_1) { |
1110 | + struct uac1_ac_header_descriptor *uac1_hdr = hdr_ptr; | 1348 | + struct uac1_as_header_descriptor *as; |
1111 | + | 1349 | + |
1112 | + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, | 1350 | + as = snd_usb_find_csint_desc(alts->extra, alts->extralen, NULL, |
1113 | + UAC_AS_GENERAL); | 1351 | + UAC_AS_GENERAL); |
1114 | + if (!as) { | 1352 | + if (!as) { |
1115 | + dev_err(uaudio_qdev->dev, | 1353 | + dev_err(&subs->dev->dev, |
1116 | + "%u:%d : no UAC_AS_GENERAL desc\n", | 1354 | + "%u:%d : no UAC_AS_GENERAL desc\n", |
1117 | + subs->cur_audiofmt->iface, | 1355 | + subs->cur_audiofmt->iface, |
1118 | + subs->cur_audiofmt->altset_idx); | 1356 | + subs->cur_audiofmt->altset_idx); |
1119 | + ret = -ENODEV; | 1357 | + return -ENODEV; |
1120 | + goto err; | ||
1121 | + } | 1358 | + } |
1359 | + | ||
1122 | + resp->data_path_delay = as->bDelay; | 1360 | + resp->data_path_delay = as->bDelay; |
1123 | + resp->data_path_delay_valid = 1; | 1361 | + resp->data_path_delay_valid = 1; |
1124 | + fmt_v1 = (struct uac_format_type_i_discrete_descriptor *)fmt; | 1362 | + |
1125 | + resp->usb_audio_subslot_size = fmt_v1->bSubframeSize; | 1363 | + resp->usb_audio_subslot_size = subs->cur_audiofmt->fmt_sz; |
1126 | + resp->usb_audio_subslot_size_valid = 1; | 1364 | + resp->usb_audio_subslot_size_valid = 1; |
1127 | + | 1365 | + |
1128 | + resp->usb_audio_spec_revision = le16_to_cpu(uac1_hdr->bcdADC); | 1366 | + resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0100); |
1129 | + resp->usb_audio_spec_revision_valid = 1; | 1367 | + resp->usb_audio_spec_revision_valid = 1; |
1130 | + } else if (protocol == UAC_VERSION_2) { | 1368 | + } else if (protocol == UAC_VERSION_2) { |
1131 | + struct uac2_ac_header_descriptor *uac2_hdr = hdr_ptr; | 1369 | + resp->usb_audio_subslot_size = subs->cur_audiofmt->fmt_sz; |
1132 | + | ||
1133 | + fmt_v2 = (struct uac_format_type_i_ext_descriptor *)fmt; | ||
1134 | + resp->usb_audio_subslot_size = fmt_v2->bSubslotSize; | ||
1135 | + resp->usb_audio_subslot_size_valid = 1; | 1370 | + resp->usb_audio_subslot_size_valid = 1; |
1136 | + | 1371 | + |
1137 | + resp->usb_audio_spec_revision = le16_to_cpu(uac2_hdr->bcdADC); | 1372 | + resp->usb_audio_spec_revision = le16_to_cpu((__force __le16)0x0200); |
1138 | + resp->usb_audio_spec_revision_valid = 1; | 1373 | + resp->usb_audio_spec_revision_valid = 1; |
1139 | + } else if (protocol == UAC_VERSION_3) { | 1374 | + } else if (protocol == UAC_VERSION_3) { |
1140 | + if (assoc->bFunctionSubClass == | 1375 | + if (iface->intf_assoc->bFunctionSubClass == |
1141 | + UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0) { | 1376 | + UAC3_FUNCTION_SUBCLASS_FULL_ADC_3_0) { |
1142 | + dev_err(uaudio_qdev->dev, "full adc is not supported\n"); | 1377 | + dev_err(&subs->dev->dev, |
1143 | + ret = -EINVAL; | 1378 | + "full adc is not supported\n"); |
1379 | + return -EINVAL; | ||
1144 | + } | 1380 | + } |
1145 | + | 1381 | + |
1146 | + switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) { | 1382 | + switch (le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize)) { |
1147 | + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: | 1383 | + case UAC3_BADD_EP_MAXPSIZE_SYNC_MONO_16: |
1148 | + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: | 1384 | + case UAC3_BADD_EP_MAXPSIZE_SYNC_STEREO_16: |
... | ... | ||
1159 | + resp->usb_audio_subslot_size = 0x3; | 1395 | + resp->usb_audio_subslot_size = 0x3; |
1160 | + break; | 1396 | + break; |
1161 | + } | 1397 | + } |
1162 | + | 1398 | + |
1163 | + default: | 1399 | + default: |
1164 | + dev_err(uaudio_qdev->dev, | 1400 | + dev_err(&subs->dev->dev, |
1165 | + "%d: %u: Invalid wMaxPacketSize\n", | 1401 | + "%d: %u: Invalid wMaxPacketSize\n", |
1166 | + subs->cur_audiofmt->iface, | 1402 | + subs->cur_audiofmt->iface, |
1167 | + subs->cur_audiofmt->altset_idx); | 1403 | + subs->cur_audiofmt->altset_idx); |
1168 | + ret = -EINVAL; | 1404 | + return -EINVAL; |
1169 | + goto err; | ||
1170 | + } | 1405 | + } |
1171 | + resp->usb_audio_subslot_size_valid = 1; | 1406 | + resp->usb_audio_subslot_size_valid = 1; |
1172 | + } else { | 1407 | + } else { |
1173 | + dev_err(uaudio_qdev->dev, "unknown protocol version %x\n", | 1408 | + dev_err(&subs->dev->dev, "unknown protocol version %x\n", |
1174 | + protocol); | 1409 | + protocol); |
1410 | + return -ENODEV; | ||
1411 | + } | ||
1412 | + | ||
1413 | + memcpy(&resp->std_as_opr_intf_desc, &alts->desc, sizeof(alts->desc)); | ||
1414 | + | ||
1415 | + return 0; | ||
1416 | +} | ||
1417 | + | ||
1418 | +/** | ||
1419 | + * prepare_qmi_response() - prepare stream enable response | ||
1420 | + * @subs: usb substream | ||
1421 | + * @req_msg: QMI request message | ||
1422 | + * @resp: QMI response buffer | ||
1423 | + * @info_idx: usb interface array index | ||
1424 | + * | ||
1425 | + * Prepares the QMI response for a USB QMI stream enable request. Will parse | ||
1426 | + * out the parameters within the stream enable request, in order to match | ||
1427 | + * requested audio profile to the ones exposed by the USB device connected. | ||
1428 | + * | ||
1429 | + * In addition, will fetch the XHCI transfer resources needed for the handoff to | ||
1430 | + * happen. This includes, transfer ring and buffer addresses and secondary event | ||
1431 | + * ring address. These parameters will be communicated as part of the USB QMI | ||
1432 | + * stream enable response. | ||
1433 | + * | ||
1434 | + */ | ||
1435 | +static int prepare_qmi_response(struct snd_usb_substream *subs, | ||
1436 | + struct qmi_uaudio_stream_req_msg_v01 *req_msg, | ||
1437 | + struct qmi_uaudio_stream_resp_msg_v01 *resp, | ||
1438 | + int info_idx) | ||
1439 | +{ | ||
1440 | + struct q6usb_offload *data; | ||
1441 | + int pcm_dev_num; | ||
1442 | + int card_num; | ||
1443 | + u8 *xfer_buf = NULL; | ||
1444 | + int ret; | ||
1445 | + | ||
1446 | + pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; | ||
1447 | + card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16; | ||
1448 | + | ||
1449 | + if (!uadev[card_num].ctrl_intf) { | ||
1450 | + dev_err(&subs->dev->dev, "audio ctrl intf info not cached\n"); | ||
1175 | + ret = -ENODEV; | 1451 | + ret = -ENODEV; |
1176 | + goto err; | 1452 | + goto err; |
1177 | + } | 1453 | + } |
1178 | + | 1454 | + |
1455 | + ret = uaudio_populate_uac_desc(subs, resp); | ||
1456 | + if (ret < 0) | ||
1457 | + goto err; | ||
1458 | + | ||
1179 | + resp->slot_id = subs->dev->slot_id; | 1459 | + resp->slot_id = subs->dev->slot_id; |
1180 | + resp->slot_id_valid = 1; | 1460 | + resp->slot_id_valid = 1; |
1181 | + | 1461 | + |
1182 | + memcpy(&resp->std_as_opr_intf_desc, &alts->desc, sizeof(alts->desc)); | ||
1183 | + resp->std_as_opr_intf_desc_valid = 1; | ||
1184 | + | ||
1185 | + ep = usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe); | ||
1186 | + if (!ep) { | ||
1187 | + dev_err(uaudio_qdev->dev, "data ep # %d context is null\n", | ||
1188 | + subs->data_endpoint->ep_num); | ||
1189 | + ret = -ENODEV; | ||
1190 | + goto err; | ||
1191 | + } | ||
1192 | + data_ep_pipe = subs->data_endpoint->pipe; | ||
1193 | + memcpy(&resp->std_as_data_ep_desc, &ep->desc, sizeof(ep->desc)); | ||
1194 | + resp->std_as_data_ep_desc_valid = 1; | ||
1195 | + | ||
1196 | + ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep); | ||
1197 | + if (ret < 0) { | ||
1198 | + dev_err(uaudio_qdev->dev, "failed to add data ep to sideband\n"); | ||
1199 | + ret = -ENODEV; | ||
1200 | + goto err; | ||
1201 | + } | ||
1202 | + | ||
1203 | + sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep); | ||
1204 | + if (!sgt) { | ||
1205 | + dev_err(uaudio_qdev->dev, "failed to get data ep ring address\n"); | ||
1206 | + ret = -ENODEV; | ||
1207 | + goto drop_data_ep; | ||
1208 | + } | ||
1209 | + | ||
1210 | + pg = sg_page(sgt->sgl); | ||
1211 | + tr_data_pa = page_to_phys(pg); | ||
1212 | + resp->xhci_mem_info.tr_data.pa = sg_dma_address(sgt->sgl); | ||
1213 | + sg_free_table(sgt); | ||
1214 | + | ||
1215 | + if (subs->sync_endpoint) { | ||
1216 | + ep = usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe); | ||
1217 | + if (!ep) { | ||
1218 | + dev_err(uaudio_qdev->dev, "implicit fb on data ep\n"); | ||
1219 | + goto skip_sync_ep; | ||
1220 | + } | ||
1221 | + sync_ep_pipe = subs->sync_endpoint->pipe; | ||
1222 | + memcpy(&resp->std_as_sync_ep_desc, &ep->desc, sizeof(ep->desc)); | ||
1223 | + resp->std_as_sync_ep_desc_valid = 1; | ||
1224 | + | ||
1225 | + ret = xhci_sideband_add_endpoint(uadev[card_num].sb, ep); | ||
1226 | + if (ret < 0) { | ||
1227 | + dev_err(uaudio_qdev->dev, | ||
1228 | + "failed to add sync ep to sideband\n"); | ||
1229 | + ret = -ENODEV; | ||
1230 | + goto drop_data_ep; | ||
1231 | + } | ||
1232 | + | ||
1233 | + sgt = xhci_sideband_get_endpoint_buffer(uadev[card_num].sb, ep); | ||
1234 | + if (!sgt) { | ||
1235 | + dev_err(uaudio_qdev->dev, "failed to get sync ep ring address\n"); | ||
1236 | + ret = -ENODEV; | ||
1237 | + goto drop_sync_ep; | ||
1238 | + } | ||
1239 | + | ||
1240 | + pg = sg_page(sgt->sgl); | ||
1241 | + tr_sync_pa = page_to_phys(pg); | ||
1242 | + resp->xhci_mem_info.tr_sync.pa = sg_dma_address(sgt->sgl); | ||
1243 | + sg_free_table(sgt); | ||
1244 | + } | ||
1245 | + | ||
1246 | +skip_sync_ep: | ||
1247 | + data = snd_soc_usb_find_priv_data(usb_get_usb_backend(subs->dev)); | 1462 | + data = snd_soc_usb_find_priv_data(usb_get_usb_backend(subs->dev)); |
1248 | + if (!data) | 1463 | + if (!data) |
1249 | + goto drop_sync_ep; | 1464 | + goto err; |
1250 | + | 1465 | + |
1251 | + uaudio_qdev->domain = data->domain; | 1466 | + uaudio_qdev->data = data; |
1252 | + uaudio_qdev->sid = data->sid; | 1467 | + |
1253 | + uaudio_qdev->intr_num = data->intr_num; | 1468 | + resp->std_as_opr_intf_desc_valid = 1; |
1254 | + uaudio_qdev->dev = data->dev; | 1469 | + ret = uaudio_endpoint_setup(subs, subs->data_endpoint, card_num, |
1470 | + &resp->xhci_mem_info.tr_data, | ||
1471 | + &resp->std_as_data_ep_desc); | ||
1472 | + if (ret < 0) | ||
1473 | + goto err; | ||
1474 | + | ||
1475 | + resp->std_as_data_ep_desc_valid = 1; | ||
1476 | + | ||
1477 | + if (subs->sync_endpoint) { | ||
1478 | + ret = uaudio_endpoint_setup(subs, subs->sync_endpoint, card_num, | ||
1479 | + &resp->xhci_mem_info.tr_sync, | ||
1480 | + &resp->std_as_sync_ep_desc); | ||
1481 | + if (ret < 0) | ||
1482 | + goto drop_data_ep; | ||
1483 | + | ||
1484 | + resp->std_as_sync_ep_desc_valid = 1; | ||
1485 | + } | ||
1255 | + | 1486 | + |
1256 | + resp->interrupter_num_valid = 1; | 1487 | + resp->interrupter_num_valid = 1; |
1257 | + resp->controller_num_valid = 0; | 1488 | + resp->controller_num_valid = 0; |
1258 | + ret = usb_get_controller_id(subs->dev); | 1489 | + ret = usb_get_controller_id(subs->dev); |
1259 | + if (ret >= 0) { | 1490 | + if (ret >= 0) { |
1260 | + resp->controller_num = ret; | 1491 | + resp->controller_num = ret; |
1261 | + resp->controller_num_valid = 1; | 1492 | + resp->controller_num_valid = 1; |
1262 | + } | 1493 | + } |
1263 | + /* map xhci data structures PA memory to iova */ | ||
1264 | + dma_coherent = dev_is_dma_coherent(subs->dev->bus->sysdev); | ||
1265 | + | 1494 | + |
1266 | + /* event ring */ | 1495 | + /* event ring */ |
1267 | + ret = xhci_sideband_create_interrupter(uadev[card_num].sb, uaudio_qdev->intr_num); | 1496 | + ret = uaudio_event_ring_setup(subs, card_num, |
1268 | + if (ret < 0) { | 1497 | + &resp->xhci_mem_info.evt_ring); |
1269 | + dev_err(uaudio_qdev->dev, "failed to fetch interrupter\n"); | 1498 | + if (ret < 0) |
1270 | + ret = -ENODEV; | ||
1271 | + goto drop_sync_ep; | 1499 | + goto drop_sync_ep; |
1272 | + } | 1500 | + |
1273 | + | 1501 | + uaudio_qdev->er_mapped = true; |
1274 | + sgt = xhci_sideband_get_event_buffer(uadev[card_num].sb); | ||
1275 | + if (!sgt) { | ||
1276 | + dev_err(uaudio_qdev->dev, "failed to get event ring address\n"); | ||
1277 | + ret = -ENODEV; | ||
1278 | + goto free_sec_ring; | ||
1279 | + } | ||
1280 | + | ||
1281 | + xhci_pa = page_to_phys(sg_page(sgt->sgl)); | ||
1282 | + resp->xhci_mem_info.evt_ring.pa = sg_dma_address(sgt->sgl); | ||
1283 | + sg_free_table(sgt); | ||
1284 | + if (!xhci_pa) { | ||
1285 | + dev_err(uaudio_qdev->dev, | ||
1286 | + "failed to get sec event ring address\n"); | ||
1287 | + ret = -ENODEV; | ||
1288 | + goto free_sec_ring; | ||
1289 | + } | ||
1290 | + | ||
1291 | + resp->interrupter_num = xhci_sideband_interrupter_id(uadev[card_num].sb); | 1502 | + resp->interrupter_num = xhci_sideband_interrupter_id(uadev[card_num].sb); |
1292 | + | ||
1293 | + va = uaudio_iommu_map(MEM_EVENT_RING, dma_coherent, xhci_pa, PAGE_SIZE, | ||
1294 | + NULL); | ||
1295 | + if (!va) { | ||
1296 | + ret = -ENOMEM; | ||
1297 | + goto free_sec_ring; | ||
1298 | + } | ||
1299 | + | ||
1300 | + resp->xhci_mem_info.evt_ring.va = PREPEND_SID_TO_IOVA(va, | ||
1301 | + uaudio_qdev->sid); | ||
1302 | + resp->xhci_mem_info.evt_ring.size = PAGE_SIZE; | ||
1303 | + uaudio_qdev->er_mapped = true; | ||
1304 | + | 1503 | + |
1305 | + resp->speed_info = get_speed_info(subs->dev->speed); | 1504 | + resp->speed_info = get_speed_info(subs->dev->speed); |
1306 | + if (resp->speed_info == USB_QMI_DEVICE_SPEED_INVALID_V01) { | 1505 | + if (resp->speed_info == USB_QMI_DEVICE_SPEED_INVALID_V01) { |
1307 | + ret = -ENODEV; | 1506 | + ret = -ENODEV; |
1308 | + goto unmap_er; | 1507 | + goto free_sec_ring; |
1309 | + } | 1508 | + } |
1310 | + | 1509 | + |
1311 | + resp->speed_info_valid = 1; | 1510 | + resp->speed_info_valid = 1; |
1312 | + | 1511 | + |
1313 | + /* data transfer ring */ | 1512 | + ret = uaudio_transfer_buffer_setup(subs, xfer_buf, req_msg->xfer_buff_size, |
1314 | + va = uaudio_iommu_map(MEM_XFER_RING, dma_coherent, tr_data_pa, | 1513 | + &resp->xhci_mem_info.xfer_buff); |
1315 | + PAGE_SIZE, NULL); | 1514 | + if (ret < 0) { |
1316 | + if (!va) { | ||
1317 | + ret = -ENOMEM; | 1515 | + ret = -ENOMEM; |
1318 | + goto unmap_er; | 1516 | + goto free_sec_ring; |
1319 | + } | 1517 | + } |
1320 | + | ||
1321 | + tr_data_va = va; | ||
1322 | + resp->xhci_mem_info.tr_data.va = PREPEND_SID_TO_IOVA(va, | ||
1323 | + uaudio_qdev->sid); | ||
1324 | + resp->xhci_mem_info.tr_data.size = PAGE_SIZE; | ||
1325 | + | ||
1326 | + /* sync transfer ring */ | ||
1327 | + if (!resp->xhci_mem_info.tr_sync.pa) | ||
1328 | + goto skip_sync; | ||
1329 | + | ||
1330 | + xhci_pa = resp->xhci_mem_info.tr_sync.pa; | ||
1331 | + va = uaudio_iommu_map(MEM_XFER_RING, dma_coherent, tr_sync_pa, | ||
1332 | + PAGE_SIZE, NULL); | ||
1333 | + if (!va) { | ||
1334 | + ret = -ENOMEM; | ||
1335 | + goto unmap_data; | ||
1336 | + } | ||
1337 | + | ||
1338 | + tr_sync_va = va; | ||
1339 | + resp->xhci_mem_info.tr_sync.va = PREPEND_SID_TO_IOVA(va, | ||
1340 | + uaudio_qdev->sid); | ||
1341 | + resp->xhci_mem_info.tr_sync.size = PAGE_SIZE; | ||
1342 | + | ||
1343 | +skip_sync: | ||
1344 | + /* xfer buffer, multiple of 4K only */ | ||
1345 | + if (!xfer_buf_len) | ||
1346 | + xfer_buf_len = PAGE_SIZE; | ||
1347 | + | ||
1348 | + mult = xfer_buf_len / PAGE_SIZE; | ||
1349 | + remainder = xfer_buf_len % PAGE_SIZE; | ||
1350 | + len = mult * PAGE_SIZE; | ||
1351 | + len += remainder ? PAGE_SIZE : 0; | ||
1352 | + | ||
1353 | + if (len > MAX_XFER_BUFF_LEN) { | ||
1354 | + dev_err(uaudio_qdev->dev, | ||
1355 | + "req buf len %d > max buf len %lu, setting %lu\n", | ||
1356 | + len, MAX_XFER_BUFF_LEN, MAX_XFER_BUFF_LEN); | ||
1357 | + len = MAX_XFER_BUFF_LEN; | ||
1358 | + } | ||
1359 | + | ||
1360 | + xfer_buf = usb_alloc_coherent(subs->dev, len, GFP_KERNEL, &xfer_buf_pa); | ||
1361 | + if (!xfer_buf) { | ||
1362 | + ret = -ENOMEM; | ||
1363 | + goto unmap_sync; | ||
1364 | + } | ||
1365 | + | ||
1366 | + dma_get_sgtable(subs->dev->bus->sysdev, &xfer_buf_sgt, xfer_buf, xfer_buf_pa, | ||
1367 | + len); | ||
1368 | + va = uaudio_iommu_map(MEM_XFER_BUF, dma_coherent, xfer_buf_pa, len, | ||
1369 | + &xfer_buf_sgt); | ||
1370 | + if (!va) { | ||
1371 | + ret = -ENOMEM; | ||
1372 | + goto unmap_sync; | ||
1373 | + } | ||
1374 | + | ||
1375 | + resp->xhci_mem_info.xfer_buff.pa = xfer_buf_pa; | ||
1376 | + resp->xhci_mem_info.xfer_buff.size = len; | ||
1377 | + | ||
1378 | + resp->xhci_mem_info.xfer_buff.va = PREPEND_SID_TO_IOVA(va, | ||
1379 | + uaudio_qdev->sid); | ||
1380 | + | 1518 | + |
1381 | + resp->xhci_mem_info_valid = 1; | 1519 | + resp->xhci_mem_info_valid = 1; |
1382 | + | ||
1383 | + sg_free_table(&xfer_buf_sgt); | ||
1384 | + | 1520 | + |
1385 | + if (!atomic_read(&uadev[card_num].in_use)) { | 1521 | + if (!atomic_read(&uadev[card_num].in_use)) { |
1386 | + kref_init(&uadev[card_num].kref); | 1522 | + kref_init(&uadev[card_num].kref); |
1387 | + init_waitqueue_head(&uadev[card_num].disconnect_wq); | 1523 | + init_waitqueue_head(&uadev[card_num].disconnect_wq); |
1388 | + uadev[card_num].num_intf = | 1524 | + uadev[card_num].num_intf = |
1389 | + subs->dev->config->desc.bNumInterfaces; | 1525 | + subs->dev->config->desc.bNumInterfaces; |
1390 | + uadev[card_num].info = kcalloc(uadev[card_num].num_intf, | 1526 | + uadev[card_num].info = kcalloc(uadev[card_num].num_intf, |
1391 | + sizeof(struct intf_info), GFP_KERNEL); | 1527 | + sizeof(struct intf_info), |
1528 | + GFP_KERNEL); | ||
1392 | + if (!uadev[card_num].info) { | 1529 | + if (!uadev[card_num].info) { |
1393 | + ret = -ENOMEM; | 1530 | + ret = -ENOMEM; |
1394 | + goto unmap_sync; | 1531 | + goto unmap_er; |
1395 | + } | 1532 | + } |
1396 | + uadev[card_num].udev = subs->dev; | 1533 | + uadev[card_num].udev = subs->dev; |
1397 | + atomic_set(&uadev[card_num].in_use, 1); | 1534 | + atomic_set(&uadev[card_num].in_use, 1); |
1398 | + } else { | 1535 | + } else { |
1399 | + kref_get(&uadev[card_num].kref); | 1536 | + kref_get(&uadev[card_num].kref); |
1400 | + } | 1537 | + } |
1401 | + | 1538 | + |
1402 | + uadev[card_num].usb_core_id = resp->controller_num; | 1539 | + uadev[card_num].usb_core_id = resp->controller_num; |
1403 | + | 1540 | + |
1404 | + /* cache intf specific info to use it for unmap and free xfer buf */ | 1541 | + /* cache intf specific info to use it for unmap and free xfer buf */ |
1405 | + uadev[card_num].info[info_idx].data_xfer_ring_va = tr_data_va; | 1542 | + uadev[card_num].info[info_idx].data_xfer_ring_va = |
1543 | + IOVA_MASK(resp->xhci_mem_info.tr_data.va); | ||
1406 | + uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE; | 1544 | + uadev[card_num].info[info_idx].data_xfer_ring_size = PAGE_SIZE; |
1407 | + uadev[card_num].info[info_idx].sync_xfer_ring_va = tr_sync_va; | 1545 | + uadev[card_num].info[info_idx].sync_xfer_ring_va = |
1546 | + IOVA_MASK(resp->xhci_mem_info.tr_sync.va); | ||
1408 | + uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE; | 1547 | + uadev[card_num].info[info_idx].sync_xfer_ring_size = PAGE_SIZE; |
1409 | + uadev[card_num].info[info_idx].xfer_buf_va = va; | 1548 | + uadev[card_num].info[info_idx].xfer_buf_va = |
1410 | + uadev[card_num].info[info_idx].xfer_buf_pa = xfer_buf_pa; | 1549 | + IOVA_MASK(resp->xhci_mem_info.xfer_buff.va); |
1411 | + uadev[card_num].info[info_idx].xfer_buf_size = len; | 1550 | + uadev[card_num].info[info_idx].xfer_buf_pa = |
1412 | + uadev[card_num].info[info_idx].data_ep_pipe = data_ep_pipe; | 1551 | + resp->xhci_mem_info.xfer_buff.pa; |
1413 | + uadev[card_num].info[info_idx].sync_ep_pipe = sync_ep_pipe; | 1552 | + uadev[card_num].info[info_idx].xfer_buf_size = |
1553 | + resp->xhci_mem_info.xfer_buff.size; | ||
1554 | + uadev[card_num].info[info_idx].data_ep_pipe = subs->data_endpoint ? | ||
1555 | + subs->data_endpoint->pipe : 0; | ||
1556 | + uadev[card_num].info[info_idx].sync_ep_pipe = subs->sync_endpoint ? | ||
1557 | + subs->sync_endpoint->pipe : 0; | ||
1558 | + uadev[card_num].info[info_idx].data_ep_idx = subs->data_endpoint ? | ||
1559 | + subs->data_endpoint->ep_num : 0; | ||
1560 | + uadev[card_num].info[info_idx].sync_ep_idx = subs->sync_endpoint ? | ||
1561 | + subs->sync_endpoint->ep_num : 0; | ||
1414 | + uadev[card_num].info[info_idx].xfer_buf = xfer_buf; | 1562 | + uadev[card_num].info[info_idx].xfer_buf = xfer_buf; |
1415 | + uadev[card_num].info[info_idx].pcm_card_num = card_num; | 1563 | + uadev[card_num].info[info_idx].pcm_card_num = card_num; |
1416 | + uadev[card_num].info[info_idx].pcm_dev_num = pcm_dev_num; | 1564 | + uadev[card_num].info[info_idx].pcm_dev_num = pcm_dev_num; |
1417 | + uadev[card_num].info[info_idx].direction = subs->direction; | 1565 | + uadev[card_num].info[info_idx].direction = subs->direction; |
1418 | + uadev[card_num].info[info_idx].intf_num = subs->cur_audiofmt->iface; | 1566 | + uadev[card_num].info[info_idx].intf_num = subs->cur_audiofmt->iface; |
1419 | + uadev[card_num].info[info_idx].in_use = true; | 1567 | + uadev[card_num].info[info_idx].in_use = true; |
1420 | + | 1568 | + |
1421 | + set_bit(card_num, &uaudio_qdev->card_slot); | 1569 | + set_bit(card_num, &uaudio_qdev->card_slot); |
1422 | + | 1570 | + |
1423 | + return 0; | 1571 | + return 0; |
1424 | + | 1572 | + |
1425 | +unmap_sync: | ||
1426 | + usb_free_coherent(subs->dev, len, xfer_buf, xfer_buf_pa); | ||
1427 | + uaudio_iommu_unmap(MEM_XFER_RING, tr_sync_va, PAGE_SIZE, PAGE_SIZE); | ||
1428 | +unmap_data: | ||
1429 | + uaudio_iommu_unmap(MEM_XFER_RING, tr_data_va, PAGE_SIZE, PAGE_SIZE); | ||
1430 | +unmap_er: | 1573 | +unmap_er: |
1431 | + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE); | 1574 | + uaudio_iommu_unmap(MEM_EVENT_RING, IOVA_BASE, PAGE_SIZE, PAGE_SIZE); |
1432 | +free_sec_ring: | 1575 | +free_sec_ring: |
1433 | + xhci_sideband_remove_interrupter(uadev[card_num].sb); | 1576 | + xhci_sideband_remove_interrupter(uadev[card_num].sb); |
1434 | +drop_sync_ep: | 1577 | +drop_sync_ep: |
1435 | + if (subs->sync_endpoint) | 1578 | + if (subs->sync_endpoint) { |
1579 | + uaudio_iommu_unmap(MEM_XFER_RING, | ||
1580 | + IOVA_MASK(resp->xhci_mem_info.tr_sync.va), | ||
1581 | + PAGE_SIZE, PAGE_SIZE); | ||
1436 | + xhci_sideband_remove_endpoint(uadev[card_num].sb, | 1582 | + xhci_sideband_remove_endpoint(uadev[card_num].sb, |
1437 | + usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe)); | 1583 | + usb_pipe_endpoint(subs->dev, subs->sync_endpoint->pipe)); |
1584 | + } | ||
1438 | +drop_data_ep: | 1585 | +drop_data_ep: |
1586 | + uaudio_iommu_unmap(MEM_XFER_RING, IOVA_MASK(resp->xhci_mem_info.tr_data.va), | ||
1587 | + PAGE_SIZE, PAGE_SIZE); | ||
1439 | + xhci_sideband_remove_endpoint(uadev[card_num].sb, | 1588 | + xhci_sideband_remove_endpoint(uadev[card_num].sb, |
1440 | + usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe)); | 1589 | + usb_pipe_endpoint(subs->dev, subs->data_endpoint->pipe)); |
1441 | + | 1590 | + |
1442 | +err: | 1591 | +err: |
1443 | + return ret; | 1592 | + return ret; |
... | ... | ||
1453 | + * Main handler for the QMI stream enable/disable requests. This executes the | 1602 | + * Main handler for the QMI stream enable/disable requests. This executes the |
1454 | + * corresponding enable/disable stream apis, respectively. | 1603 | + * corresponding enable/disable stream apis, respectively. |
1455 | + * | 1604 | + * |
1456 | + */ | 1605 | + */ |
1457 | +static void handle_uaudio_stream_req(struct qmi_handle *handle, | 1606 | +static void handle_uaudio_stream_req(struct qmi_handle *handle, |
1458 | + struct sockaddr_qrtr *sq, | 1607 | + struct sockaddr_qrtr *sq, |
1459 | + struct qmi_txn *txn, | 1608 | + struct qmi_txn *txn, |
1460 | + const void *decoded_msg) | 1609 | + const void *decoded_msg) |
1461 | +{ | 1610 | +{ |
1462 | + struct qmi_uaudio_stream_req_msg_v01 *req_msg; | 1611 | + struct qmi_uaudio_stream_req_msg_v01 *req_msg; |
1463 | + struct qmi_uaudio_stream_resp_msg_v01 resp = {{0}, 0}; | 1612 | + struct qmi_uaudio_stream_resp_msg_v01 resp = {{0}, 0}; |
1613 | + struct uaudio_qmi_svc *svc = uaudio_svc; | ||
1614 | + struct snd_usb_audio *chip = NULL; | ||
1464 | + struct snd_usb_substream *subs; | 1615 | + struct snd_usb_substream *subs; |
1465 | + struct snd_usb_audio *chip = NULL; | 1616 | + struct usb_host_endpoint *ep; |
1466 | + struct uaudio_qmi_svc *svc = uaudio_svc; | 1617 | + int datainterval = -EINVAL; |
1618 | + int info_idx = -EINVAL; | ||
1467 | + struct intf_info *info; | 1619 | + struct intf_info *info; |
1468 | + struct usb_host_endpoint *ep; | 1620 | + u8 pcm_card_num; |
1469 | + u8 pcm_card_num, pcm_dev_num, direction; | 1621 | + u8 pcm_dev_num; |
1470 | + int info_idx = -EINVAL, datainterval = -EINVAL, ret = 0; | 1622 | + u8 direction; |
1623 | + int ret = 0; | ||
1471 | + | 1624 | + |
1472 | + if (!svc->client_connected) { | 1625 | + if (!svc->client_connected) { |
1473 | + svc->client_sq = *sq; | 1626 | + svc->client_sq = *sq; |
1474 | + svc->client_connected = true; | 1627 | + svc->client_connected = true; |
1475 | + } | 1628 | + } |
... | ... | ||
1487 | + goto response; | 1640 | + goto response; |
1488 | + } | 1641 | + } |
1489 | + | 1642 | + |
1490 | + direction = (req_msg->usb_token & QMI_STREAM_REQ_DIRECTION); | 1643 | + direction = (req_msg->usb_token & QMI_STREAM_REQ_DIRECTION); |
1491 | + pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; | 1644 | + pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; |
1492 | + pcm_card_num = req_msg->enable ? uaudio_qdev->last_card_num : | 1645 | + pcm_card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16; |
1493 | + ffs(uaudio_qdev->card_slot) - 1; | ||
1494 | + if (pcm_card_num >= SNDRV_CARDS) { | 1646 | + if (pcm_card_num >= SNDRV_CARDS) { |
1495 | + ret = -EINVAL; | 1647 | + ret = -EINVAL; |
1496 | + goto response; | 1648 | + goto response; |
1497 | + } | 1649 | + } |
1498 | + | 1650 | + |
... | ... | ||
1508 | + goto response; | 1660 | + goto response; |
1509 | + } | 1661 | + } |
1510 | + | 1662 | + |
1511 | + info_idx = info_idx_from_ifnum(pcm_card_num, subs->cur_audiofmt ? | 1663 | + info_idx = info_idx_from_ifnum(pcm_card_num, subs->cur_audiofmt ? |
1512 | + subs->cur_audiofmt->iface : -1, req_msg->enable); | 1664 | + subs->cur_audiofmt->iface : -1, req_msg->enable); |
1513 | + if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm | 1665 | + if (atomic_read(&chip->shutdown) || !subs->stream || !subs->stream->pcm || |
1514 | + || !subs->stream->chip) { | 1666 | + !subs->stream->chip) { |
1515 | + ret = -ENODEV; | 1667 | + ret = -ENODEV; |
1516 | + goto response; | 1668 | + goto response; |
1517 | + } | 1669 | + } |
1518 | + | 1670 | + |
1519 | + if (req_msg->enable) { | 1671 | + if (req_msg->enable) { |
... | ... | ||
1534 | + | 1686 | + |
1535 | + uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf; | 1687 | + uadev[pcm_card_num].ctrl_intf = chip->ctrl_intf; |
1536 | + | 1688 | + |
1537 | + if (req_msg->enable) { | 1689 | + if (req_msg->enable) { |
1538 | + ret = enable_audio_stream(subs, | 1690 | + ret = enable_audio_stream(subs, |
1539 | + map_pcm_format(req_msg->audio_format), | 1691 | + map_pcm_format(req_msg->audio_format), |
1540 | + req_msg->number_of_ch, req_msg->bit_rate, | 1692 | + req_msg->number_of_ch, req_msg->bit_rate, |
1541 | + datainterval); | 1693 | + datainterval); |
1542 | + | 1694 | + |
1543 | + if (!ret) | 1695 | + if (!ret) |
1544 | + ret = prepare_qmi_response(subs, req_msg, &resp, | 1696 | + ret = prepare_qmi_response(subs, req_msg, &resp, |
1545 | + info_idx); | 1697 | + info_idx); |
1546 | + } else { | 1698 | + } else { |
1547 | + info = &uadev[pcm_card_num].info[info_idx]; | 1699 | + info = &uadev[pcm_card_num].info[info_idx]; |
1548 | + if (info->data_ep_pipe) { | 1700 | + if (info->data_ep_pipe) { |
1549 | + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, | 1701 | + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, |
1550 | + info->data_ep_pipe); | 1702 | + info->data_ep_pipe); |
1551 | + if (ep) | 1703 | + if (ep) { |
1552 | + xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb, | 1704 | + xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb, |
1553 | + ep); | 1705 | + ep); |
1554 | + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep); | 1706 | + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, |
1707 | + ep); | ||
1708 | + } | ||
1709 | + | ||
1555 | + info->data_ep_pipe = 0; | 1710 | + info->data_ep_pipe = 0; |
1556 | + } | 1711 | + } |
1557 | + | 1712 | + |
1558 | + if (info->sync_ep_pipe) { | 1713 | + if (info->sync_ep_pipe) { |
1559 | + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, | 1714 | + ep = usb_pipe_endpoint(uadev[pcm_card_num].udev, |
1560 | + info->sync_ep_pipe); | 1715 | + info->sync_ep_pipe); |
1561 | + if (ep) | 1716 | + if (ep) { |
1562 | + xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb, | 1717 | + xhci_sideband_stop_endpoint(uadev[pcm_card_num].sb, |
1563 | + ep); | 1718 | + ep); |
1564 | + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, ep); | 1719 | + xhci_sideband_remove_endpoint(uadev[pcm_card_num].sb, |
1720 | + ep); | ||
1721 | + } | ||
1722 | + | ||
1565 | + info->sync_ep_pipe = 0; | 1723 | + info->sync_ep_pipe = 0; |
1566 | + } | 1724 | + } |
1567 | + | 1725 | + |
1568 | + disable_audio_stream(subs); | 1726 | + disable_audio_stream(subs); |
1569 | + } | 1727 | + } |
1570 | + | 1728 | + |
1571 | +response: | 1729 | +response: |
1572 | + if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) { | 1730 | + if (!req_msg->enable && ret != -EINVAL && ret != -ENODEV) { |
1573 | + mutex_lock(&chip->mutex); | 1731 | + mutex_lock(&chip->mutex); |
1574 | + if (info_idx >= 0) { | 1732 | + if (info_idx >= 0) { |
1575 | + info = &uadev[pcm_card_num].info[info_idx]; | 1733 | + info = &uadev[pcm_card_num].info[info_idx]; |
1576 | + uaudio_dev_intf_cleanup( | 1734 | + uaudio_dev_intf_cleanup(uadev[pcm_card_num].udev, |
1577 | + uadev[pcm_card_num].udev, | 1735 | + info); |
1578 | + info); | ||
1579 | + } | 1736 | + } |
1580 | + if (atomic_read(&uadev[pcm_card_num].in_use)) | 1737 | + if (atomic_read(&uadev[pcm_card_num].in_use)) |
1581 | + kref_put(&uadev[pcm_card_num].kref, | 1738 | + kref_put(&uadev[pcm_card_num].kref, |
1582 | + uaudio_dev_release); | 1739 | + uaudio_dev_release); |
1583 | + mutex_unlock(&chip->mutex); | 1740 | + mutex_unlock(&chip->mutex); |
1584 | + } | 1741 | + } |
1585 | + mutex_unlock(&qdev_mutex); | 1742 | + mutex_unlock(&qdev_mutex); |
1586 | + | 1743 | + |
1587 | + resp.usb_token = req_msg->usb_token; | 1744 | + resp.usb_token = req_msg->usb_token; |
1588 | + resp.usb_token_valid = 1; | 1745 | + resp.usb_token_valid = 1; |
1589 | + resp.internal_status = ret; | 1746 | + resp.internal_status = ret; |
1590 | + resp.internal_status_valid = 1; | 1747 | + resp.internal_status_valid = 1; |
1591 | + resp.status = ret ? USB_QMI_STREAM_REQ_FAILURE_V01 : ret; | 1748 | + resp.status = ret ? USB_QMI_STREAM_REQ_FAILURE_V01 : ret; |
1592 | + resp.status_valid = 1; | 1749 | + resp.status_valid = 1; |
1593 | + ret = qmi_send_response(svc->uaudio_svc_hdl, sq, txn, | 1750 | + ret = qmi_send_response(svc->uaudio_svc_hdl, sq, txn, |
1594 | + QMI_UAUDIO_STREAM_RESP_V01, | 1751 | + QMI_UAUDIO_STREAM_RESP_V01, |
1595 | + QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN, | 1752 | + QMI_UAUDIO_STREAM_RESP_MSG_V01_MAX_MSG_LEN, |
1596 | + qmi_uaudio_stream_resp_msg_v01_ei, &resp); | 1753 | + qmi_uaudio_stream_resp_msg_v01_ei, &resp); |
1597 | +} | 1754 | +} |
1598 | + | 1755 | + |
1599 | +static struct qmi_msg_handler uaudio_stream_req_handlers = { | 1756 | +static struct qmi_msg_handler uaudio_stream_req_handlers = { |
1600 | + .type = QMI_REQUEST, | 1757 | + .type = QMI_REQUEST, |
1601 | + .msg_id = QMI_UAUDIO_STREAM_REQ_V01, | 1758 | + .msg_id = QMI_UAUDIO_STREAM_REQ_V01, |
... | ... | ||
1610 | + * Initializes the USB qdev, which is used to carry information pertaining to | 1767 | + * Initializes the USB qdev, which is used to carry information pertaining to |
1611 | + * the offloading resources. This device is freed only when there are no longer | 1768 | + * the offloading resources. This device is freed only when there are no longer |
1612 | + * any offloading candidates. (i.e, when all audio devices are disconnected) | 1769 | + * any offloading candidates. (i.e, when all audio devices are disconnected) |
1613 | + * | 1770 | + * |
1614 | + */ | 1771 | + */ |
1615 | +static int qc_usb_audio_offload_init_qmi_dev(struct usb_device *udev) | 1772 | +static int qc_usb_audio_offload_init_qmi_dev(void) |
1616 | +{ | 1773 | +{ |
1617 | + uaudio_qdev = kzalloc(sizeof(struct uaudio_qmi_dev), | 1774 | + uaudio_qdev = kzalloc(sizeof(*uaudio_qdev), GFP_KERNEL); |
1618 | + GFP_KERNEL); | ||
1619 | + if (!uaudio_qdev) | 1775 | + if (!uaudio_qdev) |
1620 | + return -ENOMEM; | 1776 | + return -ENOMEM; |
1621 | + | 1777 | + |
1622 | + /* initialize xfer ring and xfer buf iova list */ | 1778 | + /* initialize xfer ring and xfer buf iova list */ |
1623 | + INIT_LIST_HEAD(&uaudio_qdev->xfer_ring_list); | 1779 | + INIT_LIST_HEAD(&uaudio_qdev->xfer_ring_list); |
... | ... | ||
1631 | + IOVA_XFER_BUF_MAX - IOVA_XFER_BUF_BASE; | 1787 | + IOVA_XFER_BUF_MAX - IOVA_XFER_BUF_BASE; |
1632 | + | 1788 | + |
1633 | + return 0; | 1789 | + return 0; |
1634 | +} | 1790 | +} |
1635 | + | 1791 | + |
1792 | +/* Populates ppcm_idx array with supported PCM indexes */ | ||
1793 | +static int qc_usb_audio_offload_fill_avail_pcms(struct snd_usb_audio *chip, | ||
1794 | + struct snd_soc_usb_device *sdev) | ||
1795 | +{ | ||
1796 | + struct snd_usb_stream *as; | ||
1797 | + struct snd_usb_substream *subs; | ||
1798 | + int idx = 0; | ||
1799 | + | ||
1800 | + list_for_each_entry(as, &chip->pcm_list, list) { | ||
1801 | + subs = &as->substream[SNDRV_PCM_STREAM_PLAYBACK]; | ||
1802 | + if (subs->ep_num) { | ||
1803 | + sdev->ppcm_idx[idx] = as->pcm->device; | ||
1804 | + idx++; | ||
1805 | + } | ||
1806 | + /* | ||
1807 | + * Break if the current index exceeds the number of possible | ||
1808 | + * playback streams counted from the UAC descriptors. | ||
1809 | + */ | ||
1810 | + if (idx >= sdev->num_playback) | ||
1811 | + break; | ||
1812 | + } | ||
1813 | + | ||
1814 | + return -1; | ||
1815 | +} | ||
1816 | + | ||
1636 | +/** | 1817 | +/** |
1637 | + * qc_usb_audio_offload_probe() - platform op connect handler | 1818 | + * qc_usb_audio_offload_probe() - platform op connect handler |
1638 | + * @chip: USB SND device | 1819 | + * @chip: USB SND device |
1639 | + * | 1820 | + * |
1640 | + * Platform connect handler when a USB SND device is detected. Will | 1821 | + * Platform connect handler when a USB SND device is detected. Will |
1641 | + * notify SOC USB about the connection to enable the USB ASoC backend | 1822 | + * notify SOC USB about the connection to enable the USB ASoC backend |
1642 | + * and populate internal USB chip array. | 1823 | + * and populate internal USB chip array. |
1643 | + * | 1824 | + * |
1644 | + */ | 1825 | + */ |
1645 | +static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) | 1826 | +static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) |
1646 | +{ | 1827 | +{ |
1828 | + struct usb_interface *intf = chip->intf[chip->num_interfaces - 1]; | ||
1829 | + struct usb_interface_descriptor *altsd; | ||
1647 | + struct usb_device *udev = chip->dev; | 1830 | + struct usb_device *udev = chip->dev; |
1831 | + struct usb_host_interface *alts; | ||
1832 | + struct snd_soc_usb_device *sdev; | ||
1648 | + struct xhci_sideband *sb; | 1833 | + struct xhci_sideband *sb; |
1649 | + struct snd_soc_usb_device *sdev; | ||
1650 | + | 1834 | + |
1651 | + /* | 1835 | + /* |
1652 | + * If there is no priv_data, the connected device is on a USB bus | 1836 | + * If there is no priv_data, or no playback paths, the connected |
1653 | + * that doesn't support offloading. Avoid populating entries for | 1837 | + * device doesn't support offloading. Avoid populating entries for |
1654 | + * this device. | 1838 | + * this device. |
1655 | + */ | 1839 | + */ |
1656 | + if (!snd_soc_usb_find_priv_data(usb_get_usb_backend(udev))) | 1840 | + if (!snd_soc_usb_find_priv_data(usb_get_usb_backend(udev)) || |
1841 | + !usb_qmi_get_pcm_num(chip, 0)) | ||
1657 | + return; | 1842 | + return; |
1658 | + | 1843 | + |
1844 | + mutex_lock(&qdev_mutex); | ||
1659 | + mutex_lock(&chip->mutex); | 1845 | + mutex_lock(&chip->mutex); |
1660 | + if (!uadev[chip->card->number].chip) { | 1846 | + if (!uadev[chip->card->number].chip) { |
1661 | + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); | 1847 | + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); |
1662 | + if (!sdev) | 1848 | + if (!sdev) |
1663 | + goto exit; | 1849 | + goto exit; |
1664 | + | 1850 | + |
1665 | + sb = xhci_sideband_register(udev); | 1851 | + sb = xhci_sideband_register(intf, XHCI_SIDEBAND_VENDOR, |
1852 | + uaudio_sideband_notifier); | ||
1666 | + if (!sb) | 1853 | + if (!sb) |
1667 | + goto free_sdev; | 1854 | + goto free_sdev; |
1668 | + } else { | 1855 | + } else { |
1669 | + sb = uadev[chip->card->number].sb; | 1856 | + sb = uadev[chip->card->number].sb; |
1670 | + sdev = uadev[chip->card->number].sdev; | 1857 | + sdev = uadev[chip->card->number].sdev; |
1671 | + } | 1858 | + } |
1672 | + | 1859 | + |
1673 | + mutex_lock(&qdev_mutex); | ||
1674 | + if (!uaudio_qdev) | 1860 | + if (!uaudio_qdev) |
1675 | + qc_usb_audio_offload_init_qmi_dev(udev); | 1861 | + qc_usb_audio_offload_init_qmi_dev(); |
1676 | + | 1862 | + |
1677 | + atomic_inc(&uaudio_qdev->qdev_in_use); | 1863 | + atomic_inc(&uaudio_qdev->qdev_in_use); |
1678 | + mutex_unlock(&qdev_mutex); | ||
1679 | + | 1864 | + |
1680 | + uadev[chip->card->number].sb = sb; | 1865 | + uadev[chip->card->number].sb = sb; |
1681 | + uadev[chip->card->number].chip = chip; | 1866 | + uadev[chip->card->number].chip = chip; |
1682 | + | ||
1683 | + sdev->card_idx = chip->card->number; | ||
1684 | + sdev->chip_idx = chip->index; | ||
1685 | + uadev[chip->card->number].sdev = sdev; | 1867 | + uadev[chip->card->number].sdev = sdev; |
1686 | + | 1868 | + |
1687 | + uaudio_qdev->last_card_num = chip->card->number; | 1869 | + alts = &intf->altsetting[0]; |
1688 | + snd_soc_usb_connect(usb_get_usb_backend(udev), sdev); | 1870 | + altsd = get_iface_desc(alts); |
1871 | + | ||
1872 | + /* Wait until all PCM devices are populated before notifying soc-usb */ | ||
1873 | + if (altsd->bInterfaceNumber == chip->last_iface) { | ||
1874 | + sdev->num_playback = usb_qmi_get_pcm_num(chip, 0); | ||
1875 | + | ||
1876 | + /* | ||
1877 | + * Allocate playback pcm index array based on number of possible | ||
1878 | + * playback paths within the UAC descriptors. | ||
1879 | + */ | ||
1880 | + sdev->ppcm_idx = kcalloc(sdev->num_playback, sizeof(unsigned int), | ||
1881 | + GFP_KERNEL); | ||
1882 | + if (!sdev->ppcm_idx) | ||
1883 | + goto unreg_xhci; | ||
1884 | + | ||
1885 | + qc_usb_audio_offload_fill_avail_pcms(chip, sdev); | ||
1886 | + sdev->card_idx = chip->card->number; | ||
1887 | + sdev->chip_idx = chip->index; | ||
1888 | + | ||
1889 | + snd_soc_usb_connect(usb_get_usb_backend(udev), sdev); | ||
1890 | + } | ||
1891 | + | ||
1689 | + mutex_unlock(&chip->mutex); | 1892 | + mutex_unlock(&chip->mutex); |
1893 | + mutex_unlock(&qdev_mutex); | ||
1690 | + | 1894 | + |
1691 | + return; | 1895 | + return; |
1692 | + | 1896 | + |
1897 | +unreg_xhci: | ||
1898 | + xhci_sideband_unregister(sb); | ||
1899 | + uadev[chip->card->number].sb = NULL; | ||
1693 | +free_sdev: | 1900 | +free_sdev: |
1694 | + kfree(sdev); | 1901 | + kfree(sdev); |
1902 | + uadev[chip->card->number].sdev = NULL; | ||
1903 | + uadev[chip->card->number].chip = NULL; | ||
1695 | +exit: | 1904 | +exit: |
1696 | + mutex_unlock(&chip->mutex); | 1905 | + mutex_unlock(&chip->mutex); |
1906 | + mutex_unlock(&qdev_mutex); | ||
1697 | +} | 1907 | +} |
1698 | + | 1908 | + |
1699 | +/** | 1909 | +/** |
1700 | + * qc_usb_audio_cleanup_qmi_dev() - release qmi device | 1910 | + * qc_usb_audio_cleanup_qmi_dev() - release qmi device |
1701 | + * | 1911 | + * |
... | ... | ||
1717 | + * halted by issuing a QMI disconnect indication packet to the adsp. | 1927 | + * halted by issuing a QMI disconnect indication packet to the adsp. |
1718 | + * | 1928 | + * |
1719 | + */ | 1929 | + */ |
1720 | +static void qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip) | 1930 | +static void qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip) |
1721 | +{ | 1931 | +{ |
1722 | + struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; | ||
1723 | + struct uaudio_qmi_svc *svc = uaudio_svc; | ||
1724 | + struct uaudio_dev *dev; | 1932 | + struct uaudio_dev *dev; |
1725 | + int card_num; | 1933 | + int card_num; |
1726 | + int ret; | ||
1727 | + | 1934 | + |
1728 | + if (!chip) | 1935 | + if (!chip) |
1729 | + return; | 1936 | + return; |
1730 | + | 1937 | + |
1731 | + card_num = chip->card->number; | 1938 | + card_num = chip->card->number; |
... | ... | ||
1741 | + mutex_unlock(&qdev_mutex); | 1948 | + mutex_unlock(&qdev_mutex); |
1742 | + mutex_unlock(&chip->mutex); | 1949 | + mutex_unlock(&chip->mutex); |
1743 | + return; | 1950 | + return; |
1744 | + } | 1951 | + } |
1745 | + | 1952 | + |
1746 | + /* clean up */ | 1953 | + /* cleaned up already */ |
1747 | + if (!dev->udev) | 1954 | + if (!dev->udev) |
1748 | + goto done; | 1955 | + goto done; |
1749 | + | 1956 | + |
1750 | + if (atomic_read(&dev->in_use)) { | 1957 | + uaudio_send_disconnect_ind(chip); |
1751 | + mutex_unlock(&chip->mutex); | ||
1752 | + mutex_unlock(&qdev_mutex); | ||
1753 | + dev_dbg(uaudio_qdev->dev, "sending qmi indication disconnect\n"); | ||
1754 | + disconnect_ind.dev_event = USB_QMI_DEV_DISCONNECT_V01; | ||
1755 | + disconnect_ind.slot_id = dev->udev->slot_id; | ||
1756 | + disconnect_ind.controller_num = dev->usb_core_id; | ||
1757 | + disconnect_ind.controller_num_valid = 1; | ||
1758 | + ret = qmi_send_indication(svc->uaudio_svc_hdl, &svc->client_sq, | ||
1759 | + QMI_UAUDIO_STREAM_IND_V01, | ||
1760 | + QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, | ||
1761 | + qmi_uaudio_stream_ind_msg_v01_ei, | ||
1762 | + &disconnect_ind); | ||
1763 | + if (ret < 0) | ||
1764 | + dev_err(uaudio_qdev->dev, | ||
1765 | + "qmi send failed with err: %d\n", ret); | ||
1766 | + | ||
1767 | + ret = wait_event_interruptible_timeout(dev->disconnect_wq, | ||
1768 | + !atomic_read(&dev->in_use), | ||
1769 | + msecs_to_jiffies(DEV_RELEASE_WAIT_TIMEOUT)); | ||
1770 | + if (!ret) { | ||
1771 | + dev_err(uaudio_qdev->dev, | ||
1772 | + "timeout while waiting for dev_release\n"); | ||
1773 | + atomic_set(&dev->in_use, 0); | ||
1774 | + } else if (ret < 0) { | ||
1775 | + dev_err(uaudio_qdev->dev, "failed with ret %d\n", ret); | ||
1776 | + atomic_set(&dev->in_use, 0); | ||
1777 | + } | ||
1778 | + mutex_lock(&qdev_mutex); | ||
1779 | + mutex_lock(&chip->mutex); | ||
1780 | + } | ||
1781 | + | ||
1782 | + uaudio_dev_cleanup(dev); | 1958 | + uaudio_dev_cleanup(dev); |
1783 | +done: | 1959 | +done: |
1784 | + snd_soc_usb_disconnect(usb_get_usb_backend(chip->dev), dev->sdev); | ||
1785 | + | ||
1786 | + /* | 1960 | + /* |
1787 | + * If num_interfaces == 1, the last USB SND interface is being removed. | 1961 | + * If num_interfaces == 1, the last USB SND interface is being removed. |
1788 | + * This is to accommodate for devices w/ multiple UAC functions. | 1962 | + * This is to accommodate for devices w/ multiple UAC functions. |
1789 | + */ | 1963 | + */ |
1790 | + if (chip->num_interfaces == 1) { | 1964 | + if (chip->num_interfaces == 1) { |
1965 | + snd_soc_usb_disconnect(usb_get_usb_backend(chip->dev), dev->sdev); | ||
1791 | + xhci_sideband_unregister(dev->sb); | 1966 | + xhci_sideband_unregister(dev->sb); |
1792 | + dev->chip = NULL; | 1967 | + dev->chip = NULL; |
1968 | + kfree(dev->sdev->ppcm_idx); | ||
1793 | + kfree(dev->sdev); | 1969 | + kfree(dev->sdev); |
1794 | + dev->sdev = NULL; | 1970 | + dev->sdev = NULL; |
1795 | + } | 1971 | + } |
1796 | + mutex_unlock(&chip->mutex); | 1972 | + mutex_unlock(&chip->mutex); |
1797 | + | 1973 | + |
1798 | + atomic_dec(&uaudio_qdev->qdev_in_use); | 1974 | + atomic_dec(&uaudio_qdev->qdev_in_use); |
1799 | + if (!atomic_read(&uaudio_qdev->qdev_in_use)) { | 1975 | + if (!atomic_read(&uaudio_qdev->qdev_in_use)) |
1800 | + snd_soc_usb_disconnect(usb_get_usb_backend(udev)); | ||
1801 | + qc_usb_audio_cleanup_qmi_dev(); | 1976 | + qc_usb_audio_cleanup_qmi_dev(); |
1802 | + } | 1977 | + |
1803 | + mutex_unlock(&qdev_mutex); | 1978 | + mutex_unlock(&qdev_mutex); |
1804 | +} | 1979 | +} |
1805 | + | 1980 | + |
1806 | +/** | 1981 | +/** |
1807 | + * qc_usb_audio_offload_suspend() - USB offload PM suspend handler | 1982 | + * qc_usb_audio_offload_suspend() - USB offload PM suspend handler |
... | ... | ||
1810 | + * | 1985 | + * |
1811 | + * PM suspend handler to ensure that the USB offloading driver is able to stop | 1986 | + * PM suspend handler to ensure that the USB offloading driver is able to stop |
1812 | + * any pending traffic, so that the bus can be suspended. | 1987 | + * any pending traffic, so that the bus can be suspended. |
1813 | + * | 1988 | + * |
1814 | + */ | 1989 | + */ |
1815 | +static void qc_usb_audio_offload_suspend(struct usb_interface *intf, pm_message_t message) | 1990 | +static void qc_usb_audio_offload_suspend(struct usb_interface *intf, |
1991 | + pm_message_t message) | ||
1816 | +{ | 1992 | +{ |
1817 | + struct snd_usb_audio *chip = usb_get_intfdata(intf); | 1993 | + struct snd_usb_audio *chip = usb_get_intfdata(intf); |
1818 | + struct qmi_uaudio_stream_ind_msg_v01 disconnect_ind = {0}; | ||
1819 | + struct uaudio_qmi_svc *svc = uaudio_svc; | ||
1820 | + struct uaudio_dev *dev; | ||
1821 | + int card_num; | 1994 | + int card_num; |
1822 | + int ret; | ||
1823 | + | 1995 | + |
1824 | + if (!chip) | 1996 | + if (!chip) |
1825 | + return; | 1997 | + return; |
1826 | + | 1998 | + |
1827 | + card_num = chip->card->number; | 1999 | + card_num = chip->card->number; |
1828 | + if (card_num >= SNDRV_CARDS) | 2000 | + if (card_num >= SNDRV_CARDS) |
1829 | + return; | 2001 | + return; |
1830 | + | 2002 | + |
1831 | + | 2003 | + mutex_lock(&qdev_mutex); |
1832 | + mutex_lock(&chip->mutex); | 2004 | + mutex_lock(&chip->mutex); |
1833 | + dev = &uadev[card_num]; | 2005 | + |
1834 | + | 2006 | + uaudio_send_disconnect_ind(chip); |
1835 | + if (atomic_read(&dev->in_use)) { | 2007 | + |
1836 | + mutex_unlock(&chip->mutex); | 2008 | + mutex_unlock(&qdev_mutex); |
1837 | + dev_dbg(uaudio_qdev->dev, "sending qmi indication suspend\n"); | ||
1838 | + disconnect_ind.dev_event = USB_QMI_DEV_DISCONNECT_V01; | ||
1839 | + disconnect_ind.slot_id = dev->udev->slot_id; | ||
1840 | + disconnect_ind.controller_num = dev->usb_core_id; | ||
1841 | + disconnect_ind.controller_num_valid = 1; | ||
1842 | + ret = qmi_send_indication(svc->uaudio_svc_hdl, &svc->client_sq, | ||
1843 | + QMI_UAUDIO_STREAM_IND_V01, | ||
1844 | + QMI_UAUDIO_STREAM_IND_MSG_V01_MAX_MSG_LEN, | ||
1845 | + qmi_uaudio_stream_ind_msg_v01_ei, | ||
1846 | + &disconnect_ind); | ||
1847 | + if (ret < 0) | ||
1848 | + dev_err(uaudio_qdev->dev, | ||
1849 | + "qmi send failed with err: %d\n", ret); | ||
1850 | + | ||
1851 | + ret = wait_event_interruptible_timeout(dev->disconnect_wq, | ||
1852 | + !atomic_read(&dev->in_use), | ||
1853 | + msecs_to_jiffies(DEV_RELEASE_WAIT_TIMEOUT)); | ||
1854 | + if (!ret) { | ||
1855 | + dev_err(uaudio_qdev->dev, | ||
1856 | + "timeout while waiting for dev_release\n"); | ||
1857 | + atomic_set(&dev->in_use, 0); | ||
1858 | + } else if (ret < 0) { | ||
1859 | + dev_err(uaudio_qdev->dev, "failed with ret %d\n", ret); | ||
1860 | + atomic_set(&dev->in_use, 0); | ||
1861 | + } | ||
1862 | + mutex_lock(&chip->mutex); | ||
1863 | + } | ||
1864 | + mutex_unlock(&chip->mutex); | 2009 | + mutex_unlock(&chip->mutex); |
1865 | +} | 2010 | +} |
1866 | + | 2011 | + |
1867 | +static struct snd_usb_platform_ops offload_ops = { | 2012 | +static struct snd_usb_platform_ops offload_ops = { |
1868 | + .connect_cb = qc_usb_audio_offload_probe, | 2013 | + .connect_cb = qc_usb_audio_offload_probe, |
... | ... | ||
1873 | +static int __init qc_usb_audio_offload_init(void) | 2018 | +static int __init qc_usb_audio_offload_init(void) |
1874 | +{ | 2019 | +{ |
1875 | + struct uaudio_qmi_svc *svc; | 2020 | + struct uaudio_qmi_svc *svc; |
1876 | + int ret; | 2021 | + int ret; |
1877 | + | 2022 | + |
1878 | + svc = kzalloc(sizeof(struct uaudio_qmi_svc), GFP_KERNEL); | 2023 | + svc = kzalloc(sizeof(*svc), GFP_KERNEL); |
1879 | + if (!svc) | 2024 | + if (!svc) |
1880 | + return -ENOMEM; | 2025 | + return -ENOMEM; |
1881 | + | 2026 | + |
1882 | + svc->uaudio_wq = create_singlethread_workqueue("uaudio_svc"); | 2027 | + svc->uaudio_svc_hdl = kzalloc(sizeof(*svc->uaudio_svc_hdl), GFP_KERNEL); |
1883 | + if (!svc->uaudio_wq) { | 2028 | + if (!svc->uaudio_svc_hdl) { |
1884 | + ret = -ENOMEM; | 2029 | + ret = -ENOMEM; |
1885 | + goto free_svc; | 2030 | + goto free_svc; |
1886 | + } | 2031 | + } |
1887 | + | 2032 | + |
1888 | + svc->uaudio_svc_hdl = kzalloc(sizeof(struct qmi_handle), GFP_KERNEL); | ||
1889 | + if (!svc->uaudio_svc_hdl) { | ||
1890 | + ret = -ENOMEM; | ||
1891 | + goto free_wq; | ||
1892 | + } | ||
1893 | + | ||
1894 | + ret = qmi_handle_init(svc->uaudio_svc_hdl, | 2033 | + ret = qmi_handle_init(svc->uaudio_svc_hdl, |
1895 | + QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, | 2034 | + QMI_UAUDIO_STREAM_REQ_MSG_V01_MAX_MSG_LEN, |
1896 | + &uaudio_svc_ops_options, | 2035 | + &uaudio_svc_ops_options, |
1897 | + &uaudio_stream_req_handlers); | 2036 | + &uaudio_stream_req_handlers); |
1898 | + ret = qmi_add_server(svc->uaudio_svc_hdl, UAUDIO_STREAM_SERVICE_ID_V01, | 2037 | + ret = qmi_add_server(svc->uaudio_svc_hdl, UAUDIO_STREAM_SERVICE_ID_V01, |
1899 | + UAUDIO_STREAM_SERVICE_VERS_V01, 0); | 2038 | + UAUDIO_STREAM_SERVICE_VERS_V01, 0); |
1900 | + | 2039 | + |
1901 | + INIT_WORK(&svc->qmi_disconnect_work, qmi_disconnect_work); | ||
1902 | + uaudio_svc = svc; | 2040 | + uaudio_svc = svc; |
1903 | + | 2041 | + |
1904 | + ret = snd_usb_register_platform_ops(&offload_ops); | 2042 | + ret = snd_usb_register_platform_ops(&offload_ops); |
1905 | + if (ret < 0) | 2043 | + if (ret < 0) |
1906 | + goto release_qmi; | 2044 | + goto release_qmi; |
1907 | + | 2045 | + |
1908 | + return 0; | 2046 | + return 0; |
1909 | + | 2047 | + |
1910 | +release_qmi: | 2048 | +release_qmi: |
1911 | + qmi_handle_release(svc->uaudio_svc_hdl); | 2049 | + qmi_handle_release(svc->uaudio_svc_hdl); |
1912 | +free_wq: | ||
1913 | + destroy_workqueue(svc->uaudio_wq); | ||
1914 | +free_svc: | 2050 | +free_svc: |
1915 | + kfree(svc); | 2051 | + kfree(svc); |
1916 | + | 2052 | + |
1917 | + return ret; | 2053 | + return ret; |
1918 | +} | 2054 | +} |
... | ... | ||
1931 | + snd_usb_unregister_platform_ops(); | 2067 | + snd_usb_unregister_platform_ops(); |
1932 | + for (idx = 0; idx < SNDRV_CARDS; idx++) | 2068 | + for (idx = 0; idx < SNDRV_CARDS; idx++) |
1933 | + qc_usb_audio_offload_disconnect(uadev[idx].chip); | 2069 | + qc_usb_audio_offload_disconnect(uadev[idx].chip); |
1934 | + | 2070 | + |
1935 | + qmi_handle_release(svc->uaudio_svc_hdl); | 2071 | + qmi_handle_release(svc->uaudio_svc_hdl); |
1936 | + flush_workqueue(svc->uaudio_wq); | ||
1937 | + destroy_workqueue(svc->uaudio_wq); | ||
1938 | + kfree(svc); | 2072 | + kfree(svc); |
1939 | + uaudio_svc = NULL; | 2073 | + uaudio_svc = NULL; |
1940 | +} | 2074 | +} |
1941 | + | 2075 | + |
1942 | +module_init(qc_usb_audio_offload_init); | 2076 | +module_init(qc_usb_audio_offload_init); |
1943 | +module_exit(qc_usb_audio_offload_exit); | 2077 | +module_exit(qc_usb_audio_offload_exit); |
1944 | + | 2078 | + |
1945 | +MODULE_DESCRIPTION("QC USB Audio Offloading"); | 2079 | +MODULE_DESCRIPTION("QC USB Audio Offloading"); |
1946 | +MODULE_LICENSE("GPL"); | 2080 | +MODULE_LICENSE("GPL"); | diff view generated by jsdifflib |
1 | Utilize the card and PCM index coming from the USB QMI stream request. | 1 | Add proper checks and updates to the USB substream once receiving a USB QMI |
---|---|---|---|
2 | This field follows what is set by the ASoC USB backend, and could | 2 | stream enable request. If the substream is already in use from the non |
3 | potentially carry information about a specific device selected through the | 3 | offload path, reject the stream enable request. In addition, update the |
4 | ASoC USB backend. The backend also has information about the last USB | 4 | USB substream opened parameter when enabling the offload path, so the |
5 | sound device plugged in, so it can choose to select the last device plugged | 5 | non offload path can be blocked. |
6 | in, accordingly. | ||
7 | 6 | ||
8 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
9 | --- | 8 | --- |
10 | sound/usb/qcom/qc_audio_offload.c | 8 ++------ | 9 | sound/usb/qcom/qc_audio_offload.c | 15 ++++++++++++++- |
11 | 1 file changed, 2 insertions(+), 6 deletions(-) | 10 | 1 file changed, 14 insertions(+), 1 deletion(-) |
12 | 11 | ||
13 | diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c | 12 | diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c |
14 | index XXXXXXX..XXXXXXX 100644 | 13 | index XXXXXXX..XXXXXXX 100644 |
15 | --- a/sound/usb/qcom/qc_audio_offload.c | 14 | --- a/sound/usb/qcom/qc_audio_offload.c |
16 | +++ b/sound/usb/qcom/qc_audio_offload.c | 15 | +++ b/sound/usb/qcom/qc_audio_offload.c |
17 | @@ -XXX,XX +XXX,XX @@ struct uaudio_qmi_dev { | ||
18 | bool er_mapped; | ||
19 | /* reference count to number of possible consumers */ | ||
20 | atomic_t qdev_in_use; | ||
21 | - /* idx to last udev card number plugged in */ | ||
22 | - unsigned int last_card_num; | ||
23 | }; | ||
24 | |||
25 | struct uaudio_dev { | ||
26 | @@ -XXX,XX +XXX,XX @@ static int prepare_qmi_response(struct snd_usb_substream *subs, | ||
27 | assoc = iface->intf_assoc; | ||
28 | pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; | ||
29 | xfer_buf_len = req_msg->xfer_buff_size; | ||
30 | - card_num = uaudio_qdev->last_card_num; | ||
31 | + card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16; | ||
32 | |||
33 | alts = &iface->altsetting[subs->cur_audiofmt->altset_idx]; | ||
34 | altsd = get_iface_desc(alts); | ||
35 | @@ -XXX,XX +XXX,XX @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, | 16 | @@ -XXX,XX +XXX,XX @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, |
36 | |||
37 | direction = (req_msg->usb_token & QMI_STREAM_REQ_DIRECTION); | ||
38 | pcm_dev_num = (req_msg->usb_token & QMI_STREAM_REQ_DEV_NUM_MASK) >> 8; | ||
39 | - pcm_card_num = req_msg->enable ? uaudio_qdev->last_card_num : | ||
40 | - ffs(uaudio_qdev->card_slot) - 1; | ||
41 | + pcm_card_num = (req_msg->usb_token & QMI_STREAM_REQ_CARD_NUM_MASK) >> 16; | ||
42 | if (pcm_card_num >= SNDRV_CARDS) { | ||
43 | ret = -EINVAL; | ||
44 | goto response; | 17 | goto response; |
45 | @@ -XXX,XX +XXX,XX @@ static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) | 18 | } |
46 | sdev->num_capture = usb_qmi_get_pcm_num(chip, 1); | 19 | |
47 | uadev[chip->card->number].sdev = sdev; | 20 | + mutex_lock(&chip->mutex); |
48 | 21 | if (req_msg->enable) { | |
49 | - uaudio_qdev->last_card_num = chip->card->number; | 22 | - if (info_idx < 0 || chip->system_suspend) { |
50 | snd_soc_usb_connect(usb_get_usb_backend(udev), sdev); | 23 | + if (info_idx < 0 || chip->system_suspend || subs->opened) { |
51 | mutex_unlock(&chip->mutex); | 24 | ret = -EBUSY; |
25 | + mutex_unlock(&chip->mutex); | ||
26 | + | ||
27 | goto response; | ||
28 | } | ||
29 | + subs->opened = 1; | ||
30 | } | ||
31 | + mutex_unlock(&chip->mutex); | ||
32 | |||
33 | if (req_msg->service_interval_valid) { | ||
34 | ret = get_data_interval_from_si(subs, | ||
35 | @@ -XXX,XX +XXX,XX @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, | ||
36 | if (!ret) | ||
37 | ret = prepare_qmi_response(subs, req_msg, &resp, | ||
38 | info_idx); | ||
39 | + if (ret < 0) { | ||
40 | + mutex_lock(&chip->mutex); | ||
41 | + subs->opened = 0; | ||
42 | + mutex_unlock(&chip->mutex); | ||
43 | + } | ||
44 | } else { | ||
45 | info = &uadev[pcm_card_num].info[info_idx]; | ||
46 | if (info->data_ep_pipe) { | ||
47 | @@ -XXX,XX +XXX,XX @@ static void handle_uaudio_stream_req(struct qmi_handle *handle, | ||
48 | } | ||
49 | |||
50 | disable_audio_stream(subs); | ||
51 | + mutex_lock(&chip->mutex); | ||
52 | + subs->opened = 0; | ||
53 | + mutex_unlock(&chip->mutex); | ||
54 | } | ||
55 | |||
56 | response: | diff view generated by jsdifflib |
1 | Expose a kcontrol on the platform sound card, which will allow for | 1 | In order to allow userspace/applications know about USB offloading status, |
---|---|---|---|
2 | userspace to determine which USB card number and PCM device to offload. | 2 | expose a sound kcontrol that fetches information about which sound card |
3 | This allows for userspace to potentially tag an alternate path for a | 3 | and PCM index the USB device is mapped to for supporting offloading. In |
4 | specific USB SND card and PCM device. Previously, control was absent, and | 4 | the USB audio offloading framework, the ASoC BE DAI link is the entity |
5 | the offload path would be enabled on the last USB SND device which was | 5 | responsible for registering to the SOC USB layer. |
6 | connected. This logic will continue to be applicable if no mixer input is | 6 | |
7 | received for specific device selection. | 7 | It is expected for the USB SND offloading driver to add the kcontrol to the |
8 | 8 | sound card associated with the USB audio device. An example output would | |
9 | An example to configure the offload device using tinymix: | 9 | look like: |
10 | tinymix -D 0 set 'Q6USB offload SND device select' 1 0 | 10 | |
11 | 11 | tinymix -D 1 get 'USB Offload Playback Route PCM#0' | |
12 | The above will set the Q6AFE device token to choose offload on card#1 and | 12 | -1, -1 (range -1->255) |
13 | pcm#0. Device selection is made possible by setting the Q6AFE device | 13 | |
14 | token. The audio DSP utilizes this parameter, and will pass this field | 14 | This example signifies that there is no mapped ASoC path available for the |
15 | back to the USB offload driver within the QMI stream requests. | 15 | USB SND device. |
16 | |||
17 | tinymix -D 1 get 'USB Offload Playback Route PCM#0' | ||
18 | 0, 0 (range -1->255) | ||
19 | |||
20 | This example signifies that the offload path is available over ASoC sound | ||
21 | card index#0 and PCM device#0. | ||
22 | |||
23 | The USB offload kcontrol will be added in addition to the existing | ||
24 | kcontrols identified by the USB SND mixer. The kcontrols used to modify | ||
25 | the USB audio device specific parameters are still valid and expected to be | ||
26 | used. These parameters are not mirrored to the ASoC subsystem. | ||
16 | 27 | ||
17 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 28 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
18 | --- | 29 | --- |
19 | sound/soc/qcom/qdsp6/q6usb.c | 125 ++++++++++++++++++++++++++++++++++- | 30 | sound/usb/Kconfig | 10 ++ |
20 | 1 file changed, 122 insertions(+), 3 deletions(-) | 31 | sound/usb/qcom/Makefile | 4 + |
21 | 32 | sound/usb/qcom/mixer_usb_offload.c | 158 +++++++++++++++++++++++++++++ | |
22 | diff --git a/sound/soc/qcom/qdsp6/q6usb.c b/sound/soc/qcom/qdsp6/q6usb.c | 33 | sound/usb/qcom/mixer_usb_offload.h | 17 ++++ |
34 | sound/usb/qcom/qc_audio_offload.c | 2 + | ||
35 | 5 files changed, 191 insertions(+) | ||
36 | create mode 100644 sound/usb/qcom/mixer_usb_offload.c | ||
37 | create mode 100644 sound/usb/qcom/mixer_usb_offload.h | ||
38 | |||
39 | diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig | ||
23 | index XXXXXXX..XXXXXXX 100644 | 40 | index XXXXXXX..XXXXXXX 100644 |
24 | --- a/sound/soc/qcom/qdsp6/q6usb.c | 41 | --- a/sound/usb/Kconfig |
25 | +++ b/sound/soc/qcom/qdsp6/q6usb.c | 42 | +++ b/sound/usb/Kconfig |
26 | @@ -XXX,XX +XXX,XX @@ | 43 | @@ -XXX,XX +XXX,XX @@ config SND_BCD2000 |
27 | #include <linux/dma-map-ops.h> | 44 | To compile this driver as a module, choose M here: the module |
28 | 45 | will be called snd-bcd2000. | |
29 | #include <sound/pcm.h> | 46 | |
47 | +config SND_USB_QC_OFFLOAD_MIXER | ||
48 | + tristate "Qualcomm USB Audio Offload mixer control" | ||
49 | + help | ||
50 | + Say Y to enable the Qualcomm USB audio offloading mixer controls. | ||
51 | + This exposes an USB offload capable kcontrol to signal to | ||
52 | + applications about which platform sound card can support USB | ||
53 | + audio offload. The returning values specify the mapped ASoC card | ||
54 | + and PCM device the USB audio device is associated to. | ||
55 | + | ||
56 | config SND_USB_AUDIO_QMI | ||
57 | tristate "Qualcomm Audio Offload driver" | ||
58 | depends on QCOM_QMI_HELPERS && SND_USB_AUDIO && USB_XHCI_SIDEBAND && SND_SOC_USB | ||
59 | + select SND_USB_OFFLOAD_MIXER | ||
60 | help | ||
61 | Say Y here to enable the Qualcomm USB audio offloading feature. | ||
62 | |||
63 | diff --git a/sound/usb/qcom/Makefile b/sound/usb/qcom/Makefile | ||
64 | index XXXXXXX..XXXXXXX 100644 | ||
65 | --- a/sound/usb/qcom/Makefile | ||
66 | +++ b/sound/usb/qcom/Makefile | ||
67 | @@ -XXX,XX +XXX,XX @@ | ||
68 | snd-usb-audio-qmi-y := usb_audio_qmi_v01.o qc_audio_offload.o | ||
69 | obj-$(CONFIG_SND_USB_AUDIO_QMI) += snd-usb-audio-qmi.o | ||
70 | + | ||
71 | +ifneq ($(CONFIG_SND_USB_QC_OFFLOAD_MIXER),) | ||
72 | +snd-usb-audio-qmi-y += mixer_usb_offload.o | ||
73 | +endif | ||
74 | diff --git a/sound/usb/qcom/mixer_usb_offload.c b/sound/usb/qcom/mixer_usb_offload.c | ||
75 | new file mode 100644 | ||
76 | index XXXXXXX..XXXXXXX | ||
77 | --- /dev/null | ||
78 | +++ b/sound/usb/qcom/mixer_usb_offload.c | ||
79 | @@ -XXX,XX +XXX,XX @@ | ||
80 | +// SPDX-License-Identifier: GPL-2.0 | ||
81 | +/* | ||
82 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. | ||
83 | + */ | ||
84 | + | ||
85 | +#include <linux/usb.h> | ||
86 | + | ||
87 | +#include <sound/core.h> | ||
30 | +#include <sound/control.h> | 88 | +#include <sound/control.h> |
31 | #include <sound/soc.h> | 89 | +#include <sound/soc-usb.h> |
32 | #include <sound/soc-usb.h> | 90 | + |
33 | #include <sound/pcm_params.h> | 91 | +#include "../usbaudio.h" |
34 | @@ -XXX,XX +XXX,XX @@ struct q6usb_port_data { | 92 | +#include "../card.h" |
35 | struct q6afe_usb_cfg usb_cfg; | 93 | +#include "../helper.h" |
36 | struct snd_soc_usb *usb; | 94 | +#include "../mixer.h" |
37 | struct q6usb_offload priv; | 95 | + |
38 | + struct mutex mutex; | 96 | +#include "mixer_usb_offload.h" |
39 | unsigned long available_card_slot; | 97 | + |
40 | struct q6usb_status status[SNDRV_CARDS]; | 98 | +#define PCM_IDX(n) ((n) & 0xffff) |
41 | - int active_idx; | 99 | +#define CARD_IDX(n) ((n) >> 16) |
42 | + bool idx_valid; | 100 | + |
43 | + int sel_card_idx; | 101 | +static int |
44 | + int sel_pcm_idx; | 102 | +snd_usb_offload_card_route_get(struct snd_kcontrol *kcontrol, |
45 | }; | 103 | + struct snd_ctl_elem_value *ucontrol) |
46 | 104 | +{ | |
47 | static const struct snd_soc_dapm_widget q6usb_dai_widgets[] = { | 105 | + struct device *sysdev = snd_kcontrol_chip(kcontrol); |
48 | @@ -XXX,XX +XXX,XX @@ static int q6usb_hw_params(struct snd_pcm_substream *substream, | ||
49 | struct snd_soc_dai *dai) | ||
50 | { | ||
51 | struct q6usb_port_data *data = dev_get_drvdata(dai->dev); | ||
52 | + struct snd_soc_pcm_runtime *rtd = substream->private_data; | ||
53 | + struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0); | ||
54 | + struct q6afe_port *q6usb_afe; | ||
55 | int direction = substream->stream; | ||
56 | + int chip_idx; | ||
57 | + int ret; | 106 | + int ret; |
58 | + | 107 | + |
59 | + mutex_lock(&data->mutex); | 108 | + ret = snd_soc_usb_update_offload_route(sysdev, |
60 | + chip_idx = data->status[data->sel_card_idx].chip_index; | 109 | + CARD_IDX(kcontrol->private_value), |
61 | + | 110 | + PCM_IDX(kcontrol->private_value), |
62 | + ret = snd_soc_usb_find_format(chip_idx, params, direction); | 111 | + SNDRV_PCM_STREAM_PLAYBACK, |
63 | + if (ret < 0) | 112 | + SND_SOC_USB_KCTL_CARD_ROUTE, |
64 | + goto out; | 113 | + ucontrol->value.integer.value); |
65 | + | 114 | + if (ret < 0) { |
66 | + q6usb_afe = q6afe_port_get_from_id(cpu_dai->dev, USB_RX); | 115 | + ucontrol->value.integer.value[0] = -1; |
67 | + if (IS_ERR(q6usb_afe)) | 116 | + ucontrol->value.integer.value[1] = -1; |
68 | + goto out; | ||
69 | |||
70 | - return snd_soc_usb_find_format(data->active_idx, params, direction); | ||
71 | + ret = afe_port_send_usb_dev_param(q6usb_afe, data->sel_card_idx, | ||
72 | + data->sel_pcm_idx); | ||
73 | + if (ret < 0) | ||
74 | + goto out; | ||
75 | + | ||
76 | + data->status[data->sel_card_idx].pcm_index = data->sel_pcm_idx; | ||
77 | +out: | ||
78 | + mutex_unlock(&data->mutex); | ||
79 | + | ||
80 | + return ret; | ||
81 | } | ||
82 | |||
83 | static const struct snd_soc_dai_ops q6usb_ops = { | ||
84 | @@ -XXX,XX +XXX,XX @@ static struct snd_soc_dai_driver q6usb_be_dais[] = { | ||
85 | }, | ||
86 | }; | ||
87 | |||
88 | +static int q6usb_get_offload_dev(struct snd_kcontrol *kcontrol, | ||
89 | + struct snd_ctl_elem_value *ucontrol) | ||
90 | +{ | ||
91 | + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); | ||
92 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); | ||
93 | + int pcm_idx; | ||
94 | + int card_idx; | ||
95 | + | ||
96 | + mutex_lock(&data->mutex); | ||
97 | + if (!data->idx_valid) { | ||
98 | + card_idx = -1; | ||
99 | + pcm_idx = -1; | ||
100 | + } else { | ||
101 | + card_idx = data->sel_card_idx; | ||
102 | + pcm_idx = data->sel_pcm_idx; | ||
103 | + } | 117 | + } |
104 | + | 118 | + |
105 | + ucontrol->value.integer.value[0] = card_idx; | 119 | + return 0; |
106 | + ucontrol->value.integer.value[1] = pcm_idx; | 120 | +} |
107 | + mutex_unlock(&data->mutex); | 121 | + |
108 | + | 122 | +static int snd_usb_offload_card_route_info(struct snd_kcontrol *kcontrol, |
109 | + return 0; | 123 | + struct snd_ctl_elem_info *uinfo) |
110 | +} | ||
111 | + | ||
112 | +static int q6usb_put_offload_dev(struct snd_kcontrol *kcontrol, | ||
113 | + struct snd_ctl_elem_value *ucontrol) | ||
114 | +{ | ||
115 | + struct snd_soc_component *component = snd_kcontrol_chip(kcontrol); | ||
116 | + struct q6usb_port_data *data = dev_get_drvdata(component->dev); | ||
117 | + int changed = 0; | ||
118 | + int pcmidx; | ||
119 | + int cardidx; | ||
120 | + | ||
121 | + cardidx = ucontrol->value.integer.value[0]; | ||
122 | + pcmidx = ucontrol->value.integer.value[1]; | ||
123 | + | ||
124 | + mutex_lock(&data->mutex); | ||
125 | + if ((cardidx >= 0 && test_bit(cardidx, &data->available_card_slot))) { | ||
126 | + data->sel_card_idx = cardidx; | ||
127 | + changed = 1; | ||
128 | + } | ||
129 | + | ||
130 | + if ((pcmidx >= 0 && pcmidx < data->status[cardidx].num_pcm)) { | ||
131 | + data->sel_pcm_idx = pcmidx; | ||
132 | + data->idx_valid = true; | ||
133 | + changed = 1; | ||
134 | + } | ||
135 | + mutex_unlock(&data->mutex); | ||
136 | + | ||
137 | + return changed; | ||
138 | +} | ||
139 | + | ||
140 | +static int q6usb_offload_dev_info(struct snd_kcontrol *kcontrol, | ||
141 | + struct snd_ctl_elem_info *uinfo) | ||
142 | +{ | 124 | +{ |
143 | + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; | 125 | + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
144 | + uinfo->count = 2; | 126 | + uinfo->count = 1; |
145 | + uinfo->value.integer.min = -1; | 127 | + uinfo->value.integer.min = -1; |
146 | + uinfo->value.integer.max = SNDRV_CARDS; | 128 | + uinfo->value.integer.max = SNDRV_CARDS; |
147 | + | 129 | + |
148 | + return 0; | 130 | + return 0; |
149 | +} | 131 | +} |
150 | + | 132 | + |
151 | +static const struct snd_kcontrol_new q6usb_offload_dev_ctrl = { | 133 | +static struct snd_kcontrol_new snd_usb_offload_mapped_card_ctl = { |
152 | + .iface = SNDRV_CTL_ELEM_IFACE_CARD, | 134 | + .iface = SNDRV_CTL_ELEM_IFACE_CARD, |
153 | + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE, | 135 | + .access = SNDRV_CTL_ELEM_ACCESS_READ, |
154 | + .name = "Q6USB offload SND device select", | 136 | + .info = snd_usb_offload_card_route_info, |
155 | + .info = q6usb_offload_dev_info, | 137 | + .get = snd_usb_offload_card_route_get, |
156 | + .get = q6usb_get_offload_dev, | ||
157 | + .put = q6usb_put_offload_dev, | ||
158 | +}; | 138 | +}; |
159 | + | 139 | + |
160 | +/* Build a mixer control for a UAC connector control (jack-detect) */ | 140 | +static int |
161 | +static void q6usb_connector_control_init(struct snd_soc_component *component) | 141 | +snd_usb_offload_pcm_route_get(struct snd_kcontrol *kcontrol, |
162 | +{ | 142 | + struct snd_ctl_elem_value *ucontrol) |
143 | +{ | ||
144 | + struct device *sysdev = snd_kcontrol_chip(kcontrol); | ||
163 | + int ret; | 145 | + int ret; |
164 | + | 146 | + |
165 | + ret = snd_ctl_add(component->card->snd_card, | 147 | + ret = snd_soc_usb_update_offload_route(sysdev, |
166 | + snd_ctl_new1(&q6usb_offload_dev_ctrl, component)); | 148 | + CARD_IDX(kcontrol->private_value), |
167 | + if (ret < 0) | 149 | + PCM_IDX(kcontrol->private_value), |
168 | + return; | 150 | + SNDRV_PCM_STREAM_PLAYBACK, |
169 | +} | 151 | + SND_SOC_USB_KCTL_PCM_ROUTE, |
170 | + | 152 | + ucontrol->value.integer.value); |
171 | static int q6usb_audio_ports_of_xlate_dai_name(struct snd_soc_component *component, | 153 | + if (ret < 0) { |
172 | const struct of_phandle_args *args, | 154 | + ucontrol->value.integer.value[0] = -1; |
173 | const char **dai_name) | 155 | + ucontrol->value.integer.value[1] = -1; |
174 | @@ -XXX,XX +XXX,XX @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, | 156 | + } |
175 | 157 | + | |
176 | data = dev_get_drvdata(usb->component->dev); | 158 | + return 0; |
177 | 159 | +} | |
178 | + mutex_lock(&data->mutex); | 160 | + |
179 | if (connected) { | 161 | +static int snd_usb_offload_pcm_route_info(struct snd_kcontrol *kcontrol, |
180 | /* We only track the latest USB headset plugged in */ | 162 | + struct snd_ctl_elem_info *uinfo) |
181 | - data->active_idx = sdev->card_idx; | 163 | +{ |
182 | + if (!data->idx_valid || data->sel_card_idx < 0) | 164 | + uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER; |
183 | + data->sel_card_idx = sdev->card_idx; | 165 | + uinfo->count = 1; |
184 | 166 | + uinfo->value.integer.min = -1; | |
185 | set_bit(sdev->card_idx, &data->available_card_slot); | 167 | + /* Arbitrary max value, as there is no 'limit' on number of PCM devices */ |
186 | data->status[sdev->card_idx].num_pcm = sdev->num_playback; | 168 | + uinfo->value.integer.max = 0xff; |
187 | @@ -XXX,XX +XXX,XX @@ static int q6usb_alsa_connection_cb(struct snd_soc_usb *usb, | 169 | + |
188 | data->status[sdev->card_idx].num_pcm = 0; | 170 | + return 0; |
189 | data->status[sdev->card_idx].chip_index = 0; | 171 | +} |
172 | + | ||
173 | +static struct snd_kcontrol_new snd_usb_offload_mapped_pcm_ctl = { | ||
174 | + .iface = SNDRV_CTL_ELEM_IFACE_CARD, | ||
175 | + .access = SNDRV_CTL_ELEM_ACCESS_READ, | ||
176 | + .info = snd_usb_offload_pcm_route_info, | ||
177 | + .get = snd_usb_offload_pcm_route_get, | ||
178 | +}; | ||
179 | + | ||
180 | +/** | ||
181 | + * snd_usb_offload_create_ctl() - Add USB offload bounded mixer | ||
182 | + * @chip: USB SND chip device | ||
183 | + * | ||
184 | + * Creates a sound control for a USB audio device, so that applications can | ||
185 | + * query for if there is an available USB audio offload path, and which | ||
186 | + * card is managing it. | ||
187 | + */ | ||
188 | +int snd_usb_offload_create_ctl(struct snd_usb_audio *chip) | ||
189 | +{ | ||
190 | + struct usb_device *udev = chip->dev; | ||
191 | + struct snd_kcontrol_new *chip_kctl; | ||
192 | + struct snd_usb_substream *subs; | ||
193 | + struct snd_usb_stream *as; | ||
194 | + char ctl_name[48]; | ||
195 | + int ret; | ||
196 | + | ||
197 | + list_for_each_entry(as, &chip->pcm_list, list) { | ||
198 | + subs = &as->substream[SNDRV_PCM_STREAM_PLAYBACK]; | ||
199 | + if (!subs->ep_num || as->pcm_index > 0xff) | ||
200 | + continue; | ||
201 | + | ||
202 | + chip_kctl = &snd_usb_offload_mapped_card_ctl; | ||
203 | + chip_kctl->count = 1; | ||
204 | + /* | ||
205 | + * Store the associated USB SND card number and PCM index for | ||
206 | + * the kctl. | ||
207 | + */ | ||
208 | + chip_kctl->private_value = as->pcm_index | | ||
209 | + chip->card->number << 16; | ||
210 | + sprintf(ctl_name, "USB Offload Playback Card Route PCM#%d", | ||
211 | + as->pcm_index); | ||
212 | + chip_kctl->name = ctl_name; | ||
213 | + ret = snd_ctl_add(chip->card, snd_ctl_new1(chip_kctl, | ||
214 | + udev->bus->sysdev)); | ||
215 | + if (ret < 0) | ||
216 | + break; | ||
217 | + | ||
218 | + chip_kctl = &snd_usb_offload_mapped_pcm_ctl; | ||
219 | + chip_kctl->count = 1; | ||
220 | + /* | ||
221 | + * Store the associated USB SND card number and PCM index for | ||
222 | + * the kctl. | ||
223 | + */ | ||
224 | + chip_kctl->private_value = as->pcm_index | | ||
225 | + chip->card->number << 16; | ||
226 | + sprintf(ctl_name, "USB Offload Playback PCM Route PCM#%d", | ||
227 | + as->pcm_index); | ||
228 | + chip_kctl->name = ctl_name; | ||
229 | + ret = snd_ctl_add(chip->card, snd_ctl_new1(chip_kctl, | ||
230 | + udev->bus->sysdev)); | ||
231 | + if (ret < 0) | ||
232 | + break; | ||
233 | + } | ||
234 | + | ||
235 | + return ret; | ||
236 | +} | ||
237 | +EXPORT_SYMBOL_GPL(snd_usb_offload_create_ctl); | ||
238 | diff --git a/sound/usb/qcom/mixer_usb_offload.h b/sound/usb/qcom/mixer_usb_offload.h | ||
239 | new file mode 100644 | ||
240 | index XXXXXXX..XXXXXXX | ||
241 | --- /dev/null | ||
242 | +++ b/sound/usb/qcom/mixer_usb_offload.h | ||
243 | @@ -XXX,XX +XXX,XX @@ | ||
244 | +/* SPDX-License-Identifier: GPL-2.0 | ||
245 | + * | ||
246 | + * Copyright (c) 2022-2025 Qualcomm Innovation Center, Inc. All rights reserved. | ||
247 | + */ | ||
248 | + | ||
249 | +#ifndef __USB_OFFLOAD_MIXER_H | ||
250 | +#define __USB_OFFLOAD_MIXER_H | ||
251 | + | ||
252 | +#if IS_ENABLED(CONFIG_SND_USB_QC_OFFLOAD_MIXER) | ||
253 | +int snd_usb_offload_create_ctl(struct snd_usb_audio *chip); | ||
254 | +#else | ||
255 | +static inline int snd_usb_offload_create_ctl(struct snd_usb_audio *chip) | ||
256 | +{ | ||
257 | + return 0; | ||
258 | +} | ||
259 | +#endif | ||
260 | +#endif /* __USB_OFFLOAD_MIXER_H */ | ||
261 | diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c | ||
262 | index XXXXXXX..XXXXXXX 100644 | ||
263 | --- a/sound/usb/qcom/qc_audio_offload.c | ||
264 | +++ b/sound/usb/qcom/qc_audio_offload.c | ||
265 | @@ -XXX,XX +XXX,XX @@ | ||
266 | #include "../pcm.h" | ||
267 | #include "../power.h" | ||
268 | |||
269 | +#include "mixer_usb_offload.h" | ||
270 | #include "usb_audio_qmi_v01.h" | ||
271 | |||
272 | /* Stream disable request timeout during USB device disconnect */ | ||
273 | @@ -XXX,XX +XXX,XX @@ static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) | ||
274 | sdev->card_idx = chip->card->number; | ||
275 | sdev->chip_idx = chip->index; | ||
276 | |||
277 | + snd_usb_offload_create_ctl(chip); | ||
278 | snd_soc_usb_connect(usb_get_usb_backend(udev), sdev); | ||
190 | } | 279 | } |
191 | + mutex_unlock(&data->mutex); | ||
192 | |||
193 | return 0; | ||
194 | } | ||
195 | @@ -XXX,XX +XXX,XX @@ static int q6usb_component_probe(struct snd_soc_component *component) | ||
196 | { | ||
197 | struct q6usb_port_data *data = dev_get_drvdata(component->dev); | ||
198 | |||
199 | + q6usb_connector_control_init(component); | ||
200 | + | ||
201 | data->usb = snd_soc_usb_add_port(component->dev, &data->priv, q6usb_alsa_connection_cb); | ||
202 | if (IS_ERR(data->usb)) { | ||
203 | dev_err(component->dev, "failed to add usb port\n"); | ||
204 | @@ -XXX,XX +XXX,XX @@ static int q6usb_dai_dev_probe(struct platform_device *pdev) | ||
205 | |||
206 | data->priv.domain = iommu_get_domain_for_dev(&pdev->dev); | ||
207 | |||
208 | + mutex_init(&data->mutex); | ||
209 | + | ||
210 | data->priv.dev = dev; | ||
211 | dev_set_drvdata(dev, data); | diff view generated by jsdifflib |
1 | Currently, only the index to the USB SND card array is passed to the USB | 1 | If the vendor USB offload class driver is not ready/initialized before USB |
---|---|---|---|
2 | backend. Pass through more information, specifically the USB SND card | 2 | SND discovers attached devices, utilize snd_usb_rediscover_devices() to |
3 | number and the number of PCM devices available. This allows for the DPCM | 3 | find all currently attached devices, so that the ASoC entities are notified |
4 | backend to determine what USB resources are available during situations, | 4 | on available USB audio devices. |
5 | such as USB audio offloading. | ||
6 | 5 | ||
7 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> | 6 | Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> |
8 | --- | 7 | --- |
9 | sound/usb/qcom/qc_audio_offload.c | 21 ++++++++++++++++++--- | 8 | sound/usb/qcom/qc_audio_offload.c | 2 ++ |
10 | 1 file changed, 18 insertions(+), 3 deletions(-) | 9 | 1 file changed, 2 insertions(+) |
11 | 10 | ||
12 | diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c | 11 | diff --git a/sound/usb/qcom/qc_audio_offload.c b/sound/usb/qcom/qc_audio_offload.c |
13 | index XXXXXXX..XXXXXXX 100644 | 12 | index XXXXXXX..XXXXXXX 100644 |
14 | --- a/sound/usb/qcom/qc_audio_offload.c | 13 | --- a/sound/usb/qcom/qc_audio_offload.c |
15 | +++ b/sound/usb/qcom/qc_audio_offload.c | 14 | +++ b/sound/usb/qcom/qc_audio_offload.c |
16 | @@ -XXX,XX +XXX,XX @@ enum usb_qmi_audio_format { | 15 | @@ -XXX,XX +XXX,XX @@ static int __init qc_usb_audio_offload_init(void) |
17 | USB_QMI_PCM_FORMAT_U32_BE, | 16 | if (ret < 0) |
18 | }; | 17 | goto release_qmi; |
19 | 18 | ||
20 | +static int usb_qmi_get_pcm_num(struct snd_usb_audio *chip, int direction) | 19 | + snd_usb_rediscover_devices(); |
21 | +{ | ||
22 | + struct snd_usb_substream *subs = NULL; | ||
23 | + struct snd_usb_stream *as; | ||
24 | + int count = 0; | ||
25 | + | 20 | + |
26 | + list_for_each_entry(as, &chip->pcm_list, list) { | 21 | return 0; |
27 | + subs = &as->substream[direction]; | 22 | |
28 | + if (subs->ep_num) | 23 | release_qmi: |
29 | + count++; | ||
30 | + } | ||
31 | + | ||
32 | + return count; | ||
33 | +} | ||
34 | + | ||
35 | static enum usb_qmi_audio_device_speed_enum_v01 | ||
36 | get_speed_info(enum usb_device_speed udev_speed) | ||
37 | { | ||
38 | @@ -XXX,XX +XXX,XX @@ static void qc_usb_audio_offload_probe(struct snd_usb_audio *chip) | ||
39 | |||
40 | sdev->card_idx = chip->card->number; | ||
41 | sdev->chip_idx = chip->index; | ||
42 | + sdev->num_playback = usb_qmi_get_pcm_num(chip, 0); | ||
43 | + sdev->num_capture = usb_qmi_get_pcm_num(chip, 1); | ||
44 | uadev[chip->card->number].sdev = sdev; | ||
45 | |||
46 | uaudio_qdev->last_card_num = chip->card->number; | ||
47 | @@ -XXX,XX +XXX,XX @@ static void qc_usb_audio_offload_disconnect(struct snd_usb_audio *chip) | ||
48 | mutex_unlock(&chip->mutex); | ||
49 | |||
50 | atomic_dec(&uaudio_qdev->qdev_in_use); | ||
51 | - if (!atomic_read(&uaudio_qdev->qdev_in_use)) { | ||
52 | - snd_soc_usb_disconnect(usb_get_usb_backend(udev)); | ||
53 | + if (!atomic_read(&uaudio_qdev->qdev_in_use)) | ||
54 | qc_usb_audio_cleanup_qmi_dev(); | ||
55 | - } | ||
56 | mutex_unlock(&qdev_mutex); | ||
57 | } | diff view generated by jsdifflib |