1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // Copyright (c) 2018-2023, Linaro Limited. |
3 | // Copyright (c) 2018, The Linux Foundation. All rights reserved. |
4 | |
5 | #include <dt-bindings/sound/qcom,q6afe.h> |
6 | #include <linux/module.h> |
7 | #include <sound/soc.h> |
8 | #include "sdw.h" |
9 | |
10 | /** |
11 | * qcom_snd_sdw_startup() - Helper to start Soundwire stream for SoC audio card |
12 | * @substream: The PCM substream from audio, as passed to snd_soc_ops->startup() |
13 | * |
14 | * Helper for the SoC audio card (snd_soc_ops->startup()) to allocate and set |
15 | * Soundwire stream runtime to each codec DAI. |
16 | * |
17 | * The shutdown() callback should call sdw_release_stream() on the same |
18 | * sdw_stream_runtime. |
19 | * |
20 | * Return: 0 or errno |
21 | */ |
22 | int qcom_snd_sdw_startup(struct snd_pcm_substream *substream) |
23 | { |
24 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
25 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
26 | struct sdw_stream_runtime *sruntime; |
27 | struct snd_soc_dai *codec_dai; |
28 | int ret, i; |
29 | |
30 | sruntime = sdw_alloc_stream(stream_name: cpu_dai->name); |
31 | if (!sruntime) |
32 | return -ENOMEM; |
33 | |
34 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
35 | ret = snd_soc_dai_set_stream(dai: codec_dai, stream: sruntime, |
36 | direction: substream->stream); |
37 | if (ret < 0 && ret != -ENOTSUPP) { |
38 | dev_err(rtd->dev, "Failed to set sdw stream on %s\n" , |
39 | codec_dai->name); |
40 | goto err_set_stream; |
41 | } |
42 | } |
43 | |
44 | return 0; |
45 | |
46 | err_set_stream: |
47 | sdw_release_stream(stream: sruntime); |
48 | |
49 | return ret; |
50 | } |
51 | EXPORT_SYMBOL_GPL(qcom_snd_sdw_startup); |
52 | |
53 | int qcom_snd_sdw_prepare(struct snd_pcm_substream *substream, |
54 | struct sdw_stream_runtime *sruntime, |
55 | bool *stream_prepared) |
56 | { |
57 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
58 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
59 | int ret; |
60 | |
61 | if (!sruntime) |
62 | return 0; |
63 | |
64 | switch (cpu_dai->id) { |
65 | case WSA_CODEC_DMA_RX_0: |
66 | case WSA_CODEC_DMA_RX_1: |
67 | case RX_CODEC_DMA_RX_0: |
68 | case RX_CODEC_DMA_RX_1: |
69 | case TX_CODEC_DMA_TX_0: |
70 | case TX_CODEC_DMA_TX_1: |
71 | case TX_CODEC_DMA_TX_2: |
72 | case TX_CODEC_DMA_TX_3: |
73 | break; |
74 | default: |
75 | return 0; |
76 | } |
77 | |
78 | if (*stream_prepared) |
79 | return 0; |
80 | |
81 | ret = sdw_prepare_stream(stream: sruntime); |
82 | if (ret) |
83 | return ret; |
84 | |
85 | /** |
86 | * NOTE: there is a strict hw requirement about the ordering of port |
87 | * enables and actual WSA881x PA enable. PA enable should only happen |
88 | * after soundwire ports are enabled if not DC on the line is |
89 | * accumulated resulting in Click/Pop Noise |
90 | * PA enable/mute are handled as part of codec DAPM and digital mute. |
91 | */ |
92 | |
93 | ret = sdw_enable_stream(stream: sruntime); |
94 | if (ret) { |
95 | sdw_deprepare_stream(stream: sruntime); |
96 | return ret; |
97 | } |
98 | *stream_prepared = true; |
99 | |
100 | return ret; |
101 | } |
102 | EXPORT_SYMBOL_GPL(qcom_snd_sdw_prepare); |
103 | |
104 | int qcom_snd_sdw_hw_params(struct snd_pcm_substream *substream, |
105 | struct snd_pcm_hw_params *params, |
106 | struct sdw_stream_runtime **psruntime) |
107 | { |
108 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
109 | struct snd_soc_dai *codec_dai; |
110 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
111 | struct sdw_stream_runtime *sruntime; |
112 | int i; |
113 | |
114 | switch (cpu_dai->id) { |
115 | case WSA_CODEC_DMA_RX_0: |
116 | case RX_CODEC_DMA_RX_0: |
117 | case RX_CODEC_DMA_RX_1: |
118 | case TX_CODEC_DMA_TX_0: |
119 | case TX_CODEC_DMA_TX_1: |
120 | case TX_CODEC_DMA_TX_2: |
121 | case TX_CODEC_DMA_TX_3: |
122 | for_each_rtd_codec_dais(rtd, i, codec_dai) { |
123 | sruntime = snd_soc_dai_get_stream(dai: codec_dai, direction: substream->stream); |
124 | if (sruntime != ERR_PTR(error: -ENOTSUPP)) |
125 | *psruntime = sruntime; |
126 | } |
127 | break; |
128 | } |
129 | |
130 | return 0; |
131 | |
132 | } |
133 | EXPORT_SYMBOL_GPL(qcom_snd_sdw_hw_params); |
134 | |
135 | int qcom_snd_sdw_hw_free(struct snd_pcm_substream *substream, |
136 | struct sdw_stream_runtime *sruntime, bool *stream_prepared) |
137 | { |
138 | struct snd_soc_pcm_runtime *rtd = substream->private_data; |
139 | struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0); |
140 | |
141 | switch (cpu_dai->id) { |
142 | case WSA_CODEC_DMA_RX_0: |
143 | case WSA_CODEC_DMA_RX_1: |
144 | case RX_CODEC_DMA_RX_0: |
145 | case RX_CODEC_DMA_RX_1: |
146 | case TX_CODEC_DMA_TX_0: |
147 | case TX_CODEC_DMA_TX_1: |
148 | case TX_CODEC_DMA_TX_2: |
149 | case TX_CODEC_DMA_TX_3: |
150 | if (sruntime && *stream_prepared) { |
151 | sdw_disable_stream(stream: sruntime); |
152 | sdw_deprepare_stream(stream: sruntime); |
153 | *stream_prepared = false; |
154 | } |
155 | break; |
156 | default: |
157 | break; |
158 | } |
159 | |
160 | return 0; |
161 | } |
162 | EXPORT_SYMBOL_GPL(qcom_snd_sdw_hw_free); |
163 | MODULE_LICENSE("GPL" ); |
164 | |