Commit | Line | Data |
---|---|---|
6b1687bf R |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Copyright (c) 2018, The Linux Foundation. All rights reserved. | |
4 | */ | |
5 | ||
6 | #include <linux/module.h> | |
7 | #include <linux/platform_device.h> | |
6b1687bf R |
8 | #include <linux/of_device.h> |
9 | #include <sound/pcm.h> | |
10 | #include <sound/pcm_params.h> | |
6b1687bf R |
11 | #include "common.h" |
12 | #include "qdsp6/q6afe.h" | |
13 | ||
14 | #define DEFAULT_SAMPLE_RATE_48K 48000 | |
15 | #define DEFAULT_MCLK_RATE 24576000 | |
16 | #define DEFAULT_BCLK_RATE 12288000 | |
17 | ||
18 | struct sdm845_snd_data { | |
19 | struct snd_soc_card *card; | |
20 | uint32_t pri_mi2s_clk_count; | |
21 | uint32_t quat_tdm_clk_count; | |
22 | }; | |
23 | ||
24 | static unsigned int tdm_slot_offset[8] = {0, 4, 8, 12, 16, 20, 24, 28}; | |
25 | ||
26 | static int sdm845_tdm_snd_hw_params(struct snd_pcm_substream *substream, | |
27 | struct snd_pcm_hw_params *params) | |
28 | { | |
29 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
30 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | |
31 | int ret = 0; | |
32 | int channels, slot_width; | |
33 | ||
34 | switch (params_format(params)) { | |
35 | case SNDRV_PCM_FORMAT_S16_LE: | |
36 | slot_width = 32; | |
37 | break; | |
38 | default: | |
39 | dev_err(rtd->dev, "%s: invalid param format 0x%x\n", | |
40 | __func__, params_format(params)); | |
41 | return -EINVAL; | |
42 | } | |
43 | ||
44 | channels = params_channels(params); | |
45 | if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { | |
46 | ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0, 0x3, | |
47 | 8, slot_width); | |
48 | if (ret < 0) { | |
49 | dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", | |
50 | __func__, ret); | |
51 | goto end; | |
52 | } | |
53 | ||
54 | ret = snd_soc_dai_set_channel_map(cpu_dai, 0, NULL, | |
55 | channels, tdm_slot_offset); | |
56 | if (ret < 0) { | |
57 | dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n", | |
58 | __func__, ret); | |
59 | goto end; | |
60 | } | |
61 | } else { | |
62 | ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0xf, 0, | |
63 | 8, slot_width); | |
64 | if (ret < 0) { | |
65 | dev_err(rtd->dev, "%s: failed to set tdm slot, err:%d\n", | |
66 | __func__, ret); | |
67 | goto end; | |
68 | } | |
69 | ||
70 | ret = snd_soc_dai_set_channel_map(cpu_dai, channels, | |
71 | tdm_slot_offset, 0, NULL); | |
72 | if (ret < 0) { | |
73 | dev_err(rtd->dev, "%s: failed to set channel map, err:%d\n", | |
74 | __func__, ret); | |
75 | goto end; | |
76 | } | |
77 | } | |
78 | end: | |
79 | return ret; | |
80 | } | |
81 | ||
82 | static int sdm845_snd_hw_params(struct snd_pcm_substream *substream, | |
83 | struct snd_pcm_hw_params *params) | |
84 | { | |
85 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
86 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | |
87 | int ret = 0; | |
88 | ||
89 | switch (cpu_dai->id) { | |
90 | case QUATERNARY_TDM_RX_0: | |
91 | case QUATERNARY_TDM_TX_0: | |
92 | ret = sdm845_tdm_snd_hw_params(substream, params); | |
93 | break; | |
94 | default: | |
95 | pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); | |
96 | break; | |
97 | } | |
98 | return ret; | |
99 | } | |
100 | ||
101 | static int sdm845_snd_startup(struct snd_pcm_substream *substream) | |
102 | { | |
103 | unsigned int fmt = SND_SOC_DAIFMT_CBS_CFS; | |
104 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
105 | struct snd_soc_card *card = rtd->card; | |
106 | struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); | |
107 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | |
108 | ||
109 | switch (cpu_dai->id) { | |
110 | case PRIMARY_MI2S_RX: | |
111 | case PRIMARY_MI2S_TX: | |
112 | if (++(data->pri_mi2s_clk_count) == 1) { | |
113 | snd_soc_dai_set_sysclk(cpu_dai, | |
114 | Q6AFE_LPASS_CLK_ID_MCLK_1, | |
115 | DEFAULT_MCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); | |
116 | snd_soc_dai_set_sysclk(cpu_dai, | |
117 | Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT, | |
118 | DEFAULT_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); | |
119 | } | |
120 | snd_soc_dai_set_fmt(cpu_dai, fmt); | |
121 | break; | |
122 | ||
123 | case QUATERNARY_TDM_RX_0: | |
124 | case QUATERNARY_TDM_TX_0: | |
125 | if (++(data->quat_tdm_clk_count) == 1) { | |
126 | snd_soc_dai_set_sysclk(cpu_dai, | |
127 | Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT, | |
128 | DEFAULT_BCLK_RATE, SNDRV_PCM_STREAM_PLAYBACK); | |
129 | } | |
130 | break; | |
131 | ||
132 | default: | |
133 | pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); | |
134 | break; | |
135 | } | |
136 | return 0; | |
137 | } | |
138 | ||
139 | static void sdm845_snd_shutdown(struct snd_pcm_substream *substream) | |
140 | { | |
141 | struct snd_soc_pcm_runtime *rtd = substream->private_data; | |
142 | struct snd_soc_card *card = rtd->card; | |
143 | struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); | |
144 | struct snd_soc_dai *cpu_dai = rtd->cpu_dai; | |
145 | ||
146 | switch (cpu_dai->id) { | |
147 | case PRIMARY_MI2S_RX: | |
148 | case PRIMARY_MI2S_TX: | |
149 | if (--(data->pri_mi2s_clk_count) == 0) { | |
150 | snd_soc_dai_set_sysclk(cpu_dai, | |
151 | Q6AFE_LPASS_CLK_ID_MCLK_1, | |
152 | 0, SNDRV_PCM_STREAM_PLAYBACK); | |
153 | snd_soc_dai_set_sysclk(cpu_dai, | |
154 | Q6AFE_LPASS_CLK_ID_PRI_MI2S_IBIT, | |
155 | 0, SNDRV_PCM_STREAM_PLAYBACK); | |
156 | }; | |
157 | break; | |
158 | ||
159 | case QUATERNARY_TDM_RX_0: | |
160 | case QUATERNARY_TDM_TX_0: | |
161 | if (--(data->quat_tdm_clk_count) == 0) { | |
162 | snd_soc_dai_set_sysclk(cpu_dai, | |
163 | Q6AFE_LPASS_CLK_ID_QUAD_TDM_IBIT, | |
164 | 0, SNDRV_PCM_STREAM_PLAYBACK); | |
165 | } | |
166 | break; | |
167 | ||
168 | default: | |
169 | pr_err("%s: invalid dai id 0x%x\n", __func__, cpu_dai->id); | |
170 | break; | |
171 | } | |
172 | } | |
173 | ||
ff60005e | 174 | static const struct snd_soc_ops sdm845_be_ops = { |
6b1687bf R |
175 | .hw_params = sdm845_snd_hw_params, |
176 | .startup = sdm845_snd_startup, | |
177 | .shutdown = sdm845_snd_shutdown, | |
178 | }; | |
179 | ||
180 | static int sdm845_be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd, | |
181 | struct snd_pcm_hw_params *params) | |
182 | { | |
183 | struct snd_interval *rate = hw_param_interval(params, | |
184 | SNDRV_PCM_HW_PARAM_RATE); | |
185 | struct snd_interval *channels = hw_param_interval(params, | |
186 | SNDRV_PCM_HW_PARAM_CHANNELS); | |
187 | struct snd_mask *fmt = hw_param_mask(params, SNDRV_PCM_HW_PARAM_FORMAT); | |
188 | ||
189 | rate->min = rate->max = DEFAULT_SAMPLE_RATE_48K; | |
190 | channels->min = channels->max = 2; | |
191 | snd_mask_set_format(fmt, SNDRV_PCM_FORMAT_S16_LE); | |
192 | ||
193 | return 0; | |
194 | } | |
195 | ||
196 | static void sdm845_add_be_ops(struct snd_soc_card *card) | |
197 | { | |
7fe072b4 KM |
198 | struct snd_soc_dai_link *link; |
199 | int i; | |
6b1687bf | 200 | |
7fe072b4 | 201 | for_each_card_prelinks(card, i, link) { |
6b1687bf R |
202 | if (link->no_pcm == 1) { |
203 | link->ops = &sdm845_be_ops; | |
204 | link->be_hw_params_fixup = sdm845_be_hw_params_fixup; | |
205 | } | |
6b1687bf R |
206 | } |
207 | } | |
208 | ||
209 | static int sdm845_snd_platform_probe(struct platform_device *pdev) | |
210 | { | |
211 | struct snd_soc_card *card; | |
212 | struct sdm845_snd_data *data; | |
213 | struct device *dev = &pdev->dev; | |
214 | int ret; | |
215 | ||
216 | card = kzalloc(sizeof(*card), GFP_KERNEL); | |
217 | if (!card) | |
218 | return -ENOMEM; | |
219 | ||
220 | /* Allocate the private data */ | |
221 | data = kzalloc(sizeof(*data), GFP_KERNEL); | |
8530ebf1 GS |
222 | if (!data) { |
223 | ret = -ENOMEM; | |
224 | goto data_alloc_fail; | |
225 | } | |
6b1687bf R |
226 | |
227 | card->dev = dev; | |
6b1687bf R |
228 | dev_set_drvdata(dev, card); |
229 | ret = qcom_snd_parse_of(card); | |
230 | if (ret) { | |
231 | dev_err(dev, "Error parsing OF data\n"); | |
232 | goto parse_dt_fail; | |
233 | } | |
234 | ||
235 | data->card = card; | |
236 | snd_soc_card_set_drvdata(card, data); | |
237 | ||
238 | sdm845_add_be_ops(card); | |
239 | ret = snd_soc_register_card(card); | |
240 | if (ret) { | |
241 | dev_err(dev, "Sound card registration failed\n"); | |
242 | goto register_card_fail; | |
243 | } | |
244 | return ret; | |
245 | ||
246 | register_card_fail: | |
247 | kfree(card->dai_link); | |
248 | parse_dt_fail: | |
249 | kfree(data); | |
8530ebf1 | 250 | data_alloc_fail: |
6b1687bf R |
251 | kfree(card); |
252 | return ret; | |
253 | } | |
254 | ||
255 | static int sdm845_snd_platform_remove(struct platform_device *pdev) | |
256 | { | |
257 | struct snd_soc_card *card = dev_get_drvdata(&pdev->dev); | |
258 | struct sdm845_snd_data *data = snd_soc_card_get_drvdata(card); | |
259 | ||
6b1687bf R |
260 | snd_soc_unregister_card(card); |
261 | kfree(card->dai_link); | |
262 | kfree(data); | |
263 | kfree(card); | |
264 | return 0; | |
265 | } | |
266 | ||
267 | static const struct of_device_id sdm845_snd_device_id[] = { | |
268 | { .compatible = "qcom,sdm845-sndcard" }, | |
269 | {}, | |
270 | }; | |
271 | MODULE_DEVICE_TABLE(of, sdm845_snd_device_id); | |
272 | ||
273 | static struct platform_driver sdm845_snd_driver = { | |
274 | .probe = sdm845_snd_platform_probe, | |
275 | .remove = sdm845_snd_platform_remove, | |
276 | .driver = { | |
277 | .name = "msm-snd-sdm845", | |
278 | .of_match_table = sdm845_snd_device_id, | |
279 | }, | |
280 | }; | |
281 | module_platform_driver(sdm845_snd_driver); | |
282 | ||
283 | MODULE_DESCRIPTION("sdm845 ASoC Machine Driver"); | |
284 | MODULE_LICENSE("GPL v2"); |