1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | // |
3 | // soc-util.c -- ALSA SoC Audio Layer utility functions |
4 | // |
5 | // Copyright 2009 Wolfson Microelectronics PLC. |
6 | // |
7 | // Author: Mark Brown <broonie@opensource.wolfsonmicro.com> |
8 | // Liam Girdwood <lrg@slimlogic.co.uk> |
9 | |
10 | #include <linux/platform_device.h> |
11 | #include <linux/export.h> |
12 | #include <linux/math.h> |
13 | #include <sound/core.h> |
14 | #include <sound/pcm.h> |
15 | #include <sound/pcm_params.h> |
16 | #include <sound/soc.h> |
17 | |
18 | int snd_soc_calc_frame_size(int sample_size, int channels, int tdm_slots) |
19 | { |
20 | return sample_size * channels * tdm_slots; |
21 | } |
22 | EXPORT_SYMBOL_GPL(snd_soc_calc_frame_size); |
23 | |
24 | int snd_soc_params_to_frame_size(struct snd_pcm_hw_params *params) |
25 | { |
26 | int sample_size; |
27 | |
28 | sample_size = snd_pcm_format_width(format: params_format(p: params)); |
29 | if (sample_size < 0) |
30 | return sample_size; |
31 | |
32 | return snd_soc_calc_frame_size(sample_size, params_channels(p: params), |
33 | 1); |
34 | } |
35 | EXPORT_SYMBOL_GPL(snd_soc_params_to_frame_size); |
36 | |
37 | int snd_soc_calc_bclk(int fs, int sample_size, int channels, int tdm_slots) |
38 | { |
39 | return fs * snd_soc_calc_frame_size(sample_size, channels, tdm_slots); |
40 | } |
41 | EXPORT_SYMBOL_GPL(snd_soc_calc_bclk); |
42 | |
43 | int snd_soc_params_to_bclk(struct snd_pcm_hw_params *params) |
44 | { |
45 | int ret; |
46 | |
47 | ret = snd_soc_params_to_frame_size(params); |
48 | |
49 | if (ret > 0) |
50 | return ret * params_rate(p: params); |
51 | else |
52 | return ret; |
53 | } |
54 | EXPORT_SYMBOL_GPL(snd_soc_params_to_bclk); |
55 | |
56 | /** |
57 | * snd_soc_tdm_params_to_bclk - calculate bclk from params and tdm slot info. |
58 | * |
59 | * Calculate the bclk from the params sample rate, the tdm slot count and the |
60 | * tdm slot width. Optionally round-up the slot count to a given multiple. |
61 | * Either or both of tdm_width and tdm_slots can be 0. |
62 | * |
63 | * If tdm_width == 0: use params_width() as the slot width. |
64 | * If tdm_slots == 0: use params_channels() as the slot count. |
65 | * |
66 | * If slot_multiple > 1 the slot count (or params_channels() if tdm_slots == 0) |
67 | * will be rounded up to a multiple of slot_multiple. This is mainly useful for |
68 | * I2S mode, which has a left and right phase so the number of slots is always |
69 | * a multiple of 2. |
70 | * |
71 | * If tdm_width == 0 && tdm_slots == 0 && slot_multiple < 2, this is equivalent |
72 | * to calling snd_soc_params_to_bclk(). |
73 | * |
74 | * @params: Pointer to struct_pcm_hw_params. |
75 | * @tdm_width: Width in bits of the tdm slots. Must be >= 0. |
76 | * @tdm_slots: Number of tdm slots per frame. Must be >= 0. |
77 | * @slot_multiple: If >1 roundup slot count to a multiple of this value. |
78 | * |
79 | * Return: bclk frequency in Hz, else a negative error code if params format |
80 | * is invalid. |
81 | */ |
82 | int snd_soc_tdm_params_to_bclk(struct snd_pcm_hw_params *params, |
83 | int tdm_width, int tdm_slots, int slot_multiple) |
84 | { |
85 | if (!tdm_slots) |
86 | tdm_slots = params_channels(p: params); |
87 | |
88 | if (slot_multiple > 1) |
89 | tdm_slots = roundup(tdm_slots, slot_multiple); |
90 | |
91 | if (!tdm_width) { |
92 | tdm_width = snd_pcm_format_width(format: params_format(p: params)); |
93 | if (tdm_width < 0) |
94 | return tdm_width; |
95 | } |
96 | |
97 | return snd_soc_calc_bclk(params_rate(p: params), tdm_width, 1, tdm_slots); |
98 | } |
99 | EXPORT_SYMBOL_GPL(snd_soc_tdm_params_to_bclk); |
100 | |
101 | static const struct snd_pcm_hardware dummy_dma_hardware = { |
102 | /* Random values to keep userspace happy when checking constraints */ |
103 | .info = SNDRV_PCM_INFO_INTERLEAVED | |
104 | SNDRV_PCM_INFO_BLOCK_TRANSFER, |
105 | .buffer_bytes_max = 128*1024, |
106 | .period_bytes_min = PAGE_SIZE, |
107 | .period_bytes_max = PAGE_SIZE*2, |
108 | .periods_min = 2, |
109 | .periods_max = 128, |
110 | }; |
111 | |
112 | |
113 | static const struct snd_soc_component_driver dummy_platform; |
114 | |
115 | static int dummy_dma_open(struct snd_soc_component *component, |
116 | struct snd_pcm_substream *substream) |
117 | { |
118 | struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream); |
119 | int i; |
120 | |
121 | /* |
122 | * If there are other components associated with rtd, we shouldn't |
123 | * override their hwparams |
124 | */ |
125 | for_each_rtd_components(rtd, i, component) { |
126 | if (component->driver == &dummy_platform) |
127 | return 0; |
128 | } |
129 | |
130 | /* BE's dont need dummy params */ |
131 | if (!rtd->dai_link->no_pcm) |
132 | snd_soc_set_runtime_hwparams(substream, hw: &dummy_dma_hardware); |
133 | |
134 | return 0; |
135 | } |
136 | |
137 | static const struct snd_soc_component_driver dummy_platform = { |
138 | .open = dummy_dma_open, |
139 | }; |
140 | |
141 | static const struct snd_soc_component_driver dummy_codec = { |
142 | .idle_bias_on = 1, |
143 | .use_pmdown_time = 1, |
144 | .endianness = 1, |
145 | }; |
146 | |
147 | #define STUB_RATES SNDRV_PCM_RATE_8000_384000 |
148 | #define STUB_FORMATS (SNDRV_PCM_FMTBIT_S8 | \ |
149 | SNDRV_PCM_FMTBIT_U8 | \ |
150 | SNDRV_PCM_FMTBIT_S16_LE | \ |
151 | SNDRV_PCM_FMTBIT_U16_LE | \ |
152 | SNDRV_PCM_FMTBIT_S24_LE | \ |
153 | SNDRV_PCM_FMTBIT_S24_3LE | \ |
154 | SNDRV_PCM_FMTBIT_U24_LE | \ |
155 | SNDRV_PCM_FMTBIT_S32_LE | \ |
156 | SNDRV_PCM_FMTBIT_U32_LE | \ |
157 | SNDRV_PCM_FMTBIT_IEC958_SUBFRAME_LE) |
158 | |
159 | /* |
160 | * Select these from Sound Card Manually |
161 | * SND_SOC_POSSIBLE_DAIFMT_CBP_CFP |
162 | * SND_SOC_POSSIBLE_DAIFMT_CBP_CFC |
163 | * SND_SOC_POSSIBLE_DAIFMT_CBC_CFP |
164 | * SND_SOC_POSSIBLE_DAIFMT_CBC_CFC |
165 | */ |
166 | static u64 dummy_dai_formats = |
167 | SND_SOC_POSSIBLE_DAIFMT_I2S | |
168 | SND_SOC_POSSIBLE_DAIFMT_RIGHT_J | |
169 | SND_SOC_POSSIBLE_DAIFMT_LEFT_J | |
170 | SND_SOC_POSSIBLE_DAIFMT_DSP_A | |
171 | SND_SOC_POSSIBLE_DAIFMT_DSP_B | |
172 | SND_SOC_POSSIBLE_DAIFMT_AC97 | |
173 | SND_SOC_POSSIBLE_DAIFMT_PDM | |
174 | SND_SOC_POSSIBLE_DAIFMT_GATED | |
175 | SND_SOC_POSSIBLE_DAIFMT_CONT | |
176 | SND_SOC_POSSIBLE_DAIFMT_NB_NF | |
177 | SND_SOC_POSSIBLE_DAIFMT_NB_IF | |
178 | SND_SOC_POSSIBLE_DAIFMT_IB_NF | |
179 | SND_SOC_POSSIBLE_DAIFMT_IB_IF; |
180 | |
181 | static const struct snd_soc_dai_ops dummy_dai_ops = { |
182 | .auto_selectable_formats = &dummy_dai_formats, |
183 | .num_auto_selectable_formats = 1, |
184 | }; |
185 | |
186 | /* |
187 | * The dummy CODEC is only meant to be used in situations where there is no |
188 | * actual hardware. |
189 | * |
190 | * If there is actual hardware even if it does not have a control bus |
191 | * the hardware will still have constraints like supported samplerates, etc. |
192 | * which should be modelled. And the data flow graph also should be modelled |
193 | * using DAPM. |
194 | */ |
195 | static struct snd_soc_dai_driver dummy_dai = { |
196 | .name = "snd-soc-dummy-dai" , |
197 | .playback = { |
198 | .stream_name = "Playback" , |
199 | .channels_min = 1, |
200 | .channels_max = 384, |
201 | .rates = STUB_RATES, |
202 | .formats = STUB_FORMATS, |
203 | }, |
204 | .capture = { |
205 | .stream_name = "Capture" , |
206 | .channels_min = 1, |
207 | .channels_max = 384, |
208 | .rates = STUB_RATES, |
209 | .formats = STUB_FORMATS, |
210 | }, |
211 | .ops = &dummy_dai_ops, |
212 | }; |
213 | |
214 | int snd_soc_dai_is_dummy(struct snd_soc_dai *dai) |
215 | { |
216 | if (dai->driver == &dummy_dai) |
217 | return 1; |
218 | return 0; |
219 | } |
220 | EXPORT_SYMBOL_GPL(snd_soc_dai_is_dummy); |
221 | |
222 | int snd_soc_component_is_dummy(struct snd_soc_component *component) |
223 | { |
224 | return ((component->driver == &dummy_platform) || |
225 | (component->driver == &dummy_codec)); |
226 | } |
227 | |
228 | struct snd_soc_dai_link_component snd_soc_dummy_dlc = { |
229 | .of_node = NULL, |
230 | .dai_name = "snd-soc-dummy-dai" , |
231 | .name = "snd-soc-dummy" , |
232 | }; |
233 | EXPORT_SYMBOL_GPL(snd_soc_dummy_dlc); |
234 | |
235 | static int snd_soc_dummy_probe(struct platform_device *pdev) |
236 | { |
237 | int ret; |
238 | |
239 | ret = devm_snd_soc_register_component(dev: &pdev->dev, |
240 | component_driver: &dummy_codec, dai_drv: &dummy_dai, num_dai: 1); |
241 | if (ret < 0) |
242 | return ret; |
243 | |
244 | ret = devm_snd_soc_register_component(dev: &pdev->dev, component_driver: &dummy_platform, |
245 | NULL, num_dai: 0); |
246 | |
247 | return ret; |
248 | } |
249 | |
250 | static struct platform_driver soc_dummy_driver = { |
251 | .driver = { |
252 | .name = "snd-soc-dummy" , |
253 | }, |
254 | .probe = snd_soc_dummy_probe, |
255 | }; |
256 | |
257 | static struct platform_device *soc_dummy_dev; |
258 | |
259 | int __init snd_soc_util_init(void) |
260 | { |
261 | int ret; |
262 | |
263 | soc_dummy_dev = |
264 | platform_device_register_simple(name: "snd-soc-dummy" , id: -1, NULL, num: 0); |
265 | if (IS_ERR(ptr: soc_dummy_dev)) |
266 | return PTR_ERR(ptr: soc_dummy_dev); |
267 | |
268 | ret = platform_driver_register(&soc_dummy_driver); |
269 | if (ret != 0) |
270 | platform_device_unregister(soc_dummy_dev); |
271 | |
272 | return ret; |
273 | } |
274 | |
275 | void snd_soc_util_exit(void) |
276 | { |
277 | platform_driver_unregister(&soc_dummy_driver); |
278 | platform_device_unregister(soc_dummy_dev); |
279 | } |
280 | |