Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/sound/soc/qcom/qdsp6/q6apm-lpass-dais.c
26444 views
1
// SPDX-License-Identifier: GPL-2.0
2
// Copyright (c) 2021, Linaro Limited
3
4
#include <dt-bindings/sound/qcom,q6dsp-lpass-ports.h>
5
#include <linux/err.h>
6
#include <linux/init.h>
7
#include <linux/module.h>
8
#include <linux/device.h>
9
#include <linux/platform_device.h>
10
#include <linux/slab.h>
11
#include <sound/pcm.h>
12
#include <sound/soc.h>
13
#include <sound/pcm_params.h>
14
#include "q6dsp-lpass-ports.h"
15
#include "q6dsp-common.h"
16
#include "audioreach.h"
17
#include "q6apm.h"
18
19
#define AUDIOREACH_BE_PCM_BASE 16
20
21
struct q6apm_lpass_dai_data {
22
struct q6apm_graph *graph[APM_PORT_MAX];
23
bool is_port_started[APM_PORT_MAX];
24
struct audioreach_module_config module_config[APM_PORT_MAX];
25
};
26
27
static int q6dma_set_channel_map(struct snd_soc_dai *dai,
28
unsigned int tx_num,
29
const unsigned int *tx_ch_mask,
30
unsigned int rx_num,
31
const unsigned int *rx_ch_mask)
32
{
33
34
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
35
struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
36
int i;
37
38
switch (dai->id) {
39
case WSA_CODEC_DMA_TX_0:
40
case WSA_CODEC_DMA_TX_1:
41
case WSA_CODEC_DMA_TX_2:
42
case VA_CODEC_DMA_TX_0:
43
case VA_CODEC_DMA_TX_1:
44
case VA_CODEC_DMA_TX_2:
45
case TX_CODEC_DMA_TX_0:
46
case TX_CODEC_DMA_TX_1:
47
case TX_CODEC_DMA_TX_2:
48
case TX_CODEC_DMA_TX_3:
49
case TX_CODEC_DMA_TX_4:
50
case TX_CODEC_DMA_TX_5:
51
if (!tx_ch_mask) {
52
dev_err(dai->dev, "tx slot not found\n");
53
return -EINVAL;
54
}
55
56
if (tx_num > AR_PCM_MAX_NUM_CHANNEL) {
57
dev_err(dai->dev, "invalid tx num %d\n",
58
tx_num);
59
return -EINVAL;
60
}
61
for (i = 0; i < tx_num; i++)
62
cfg->channel_map[i] = tx_ch_mask[i];
63
64
break;
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 RX_CODEC_DMA_RX_2:
70
case RX_CODEC_DMA_RX_3:
71
case RX_CODEC_DMA_RX_4:
72
case RX_CODEC_DMA_RX_5:
73
case RX_CODEC_DMA_RX_6:
74
case RX_CODEC_DMA_RX_7:
75
/* rx */
76
if (!rx_ch_mask) {
77
dev_err(dai->dev, "rx slot not found\n");
78
return -EINVAL;
79
}
80
if (rx_num > APM_PORT_MAX_AUDIO_CHAN_CNT) {
81
dev_err(dai->dev, "invalid rx num %d\n",
82
rx_num);
83
return -EINVAL;
84
}
85
for (i = 0; i < rx_num; i++)
86
cfg->channel_map[i] = rx_ch_mask[i];
87
88
break;
89
default:
90
dev_err(dai->dev, "%s: invalid dai id 0x%x\n",
91
__func__, dai->id);
92
return -EINVAL;
93
}
94
95
return 0;
96
}
97
98
static int q6hdmi_hw_params(struct snd_pcm_substream *substream,
99
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
100
{
101
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
102
struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
103
int channels = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max;
104
int ret;
105
106
cfg->bit_width = params_width(params);
107
cfg->sample_rate = params_rate(params);
108
cfg->num_channels = channels;
109
audioreach_set_default_channel_mapping(cfg->channel_map, channels);
110
111
switch (dai->id) {
112
case DISPLAY_PORT_RX_0:
113
cfg->dp_idx = 0;
114
break;
115
case DISPLAY_PORT_RX_1 ... DISPLAY_PORT_RX_7:
116
cfg->dp_idx = dai->id - DISPLAY_PORT_RX_1 + 1;
117
break;
118
}
119
120
ret = q6dsp_get_channel_allocation(channels);
121
if (ret < 0)
122
return ret;
123
124
cfg->channel_allocation = ret;
125
126
return 0;
127
}
128
129
static int q6dma_hw_params(struct snd_pcm_substream *substream,
130
struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
131
{
132
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
133
struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
134
int channels = hw_param_interval_c(params, SNDRV_PCM_HW_PARAM_CHANNELS)->max;
135
136
cfg->bit_width = params_width(params);
137
cfg->sample_rate = params_rate(params);
138
cfg->num_channels = channels;
139
audioreach_set_default_channel_mapping(cfg->channel_map, channels);
140
141
return 0;
142
}
143
144
static void q6apm_lpass_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
145
{
146
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
147
int rc;
148
149
if (dai_data->is_port_started[dai->id]) {
150
rc = q6apm_graph_stop(dai_data->graph[dai->id]);
151
dai_data->is_port_started[dai->id] = false;
152
if (rc < 0)
153
dev_err(dai->dev, "fail to close APM port (%d)\n", rc);
154
}
155
156
if (dai_data->graph[dai->id]) {
157
q6apm_graph_close(dai_data->graph[dai->id]);
158
dai_data->graph[dai->id] = NULL;
159
}
160
}
161
162
static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
163
{
164
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
165
struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
166
struct q6apm_graph *graph;
167
int graph_id = dai->id;
168
int rc;
169
170
if (dai_data->is_port_started[dai->id]) {
171
q6apm_graph_stop(dai_data->graph[dai->id]);
172
dai_data->is_port_started[dai->id] = false;
173
174
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
175
q6apm_graph_close(dai_data->graph[dai->id]);
176
dai_data->graph[dai->id] = NULL;
177
}
178
}
179
180
/**
181
* It is recommend to load DSP with source graph first and then sink
182
* graph, so sequence for playback and capture will be different
183
*/
184
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
185
graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id);
186
if (IS_ERR(graph)) {
187
dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id);
188
rc = PTR_ERR(graph);
189
return rc;
190
}
191
dai_data->graph[graph_id] = graph;
192
}
193
194
cfg->direction = substream->stream;
195
rc = q6apm_graph_media_format_pcm(dai_data->graph[dai->id], cfg);
196
if (rc) {
197
dev_err(dai->dev, "Failed to set media format %d\n", rc);
198
goto err;
199
}
200
201
rc = q6apm_graph_prepare(dai_data->graph[dai->id]);
202
if (rc) {
203
dev_err(dai->dev, "Failed to prepare Graph %d\n", rc);
204
goto err;
205
}
206
207
rc = q6apm_graph_start(dai_data->graph[dai->id]);
208
if (rc < 0) {
209
dev_err(dai->dev, "Failed to start APM port %d\n", dai->id);
210
goto err;
211
}
212
dai_data->is_port_started[dai->id] = true;
213
214
return 0;
215
err:
216
q6apm_graph_close(dai_data->graph[dai->id]);
217
dai_data->graph[dai->id] = NULL;
218
return rc;
219
}
220
221
static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
222
{
223
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
224
struct q6apm_graph *graph;
225
int graph_id = dai->id;
226
227
if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
228
graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id);
229
if (IS_ERR(graph)) {
230
dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id);
231
return PTR_ERR(graph);
232
}
233
dai_data->graph[graph_id] = graph;
234
}
235
236
return 0;
237
}
238
239
static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
240
{
241
struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev);
242
struct audioreach_module_config *cfg = &dai_data->module_config[dai->id];
243
244
cfg->fmt = fmt;
245
246
return 0;
247
}
248
249
static const struct snd_soc_dai_ops q6dma_ops = {
250
.prepare = q6apm_lpass_dai_prepare,
251
.startup = q6apm_lpass_dai_startup,
252
.shutdown = q6apm_lpass_dai_shutdown,
253
.set_channel_map = q6dma_set_channel_map,
254
.hw_params = q6dma_hw_params,
255
};
256
257
static const struct snd_soc_dai_ops q6i2s_ops = {
258
.prepare = q6apm_lpass_dai_prepare,
259
.startup = q6apm_lpass_dai_startup,
260
.shutdown = q6apm_lpass_dai_shutdown,
261
.set_channel_map = q6dma_set_channel_map,
262
.hw_params = q6dma_hw_params,
263
};
264
265
static const struct snd_soc_dai_ops q6hdmi_ops = {
266
.prepare = q6apm_lpass_dai_prepare,
267
.startup = q6apm_lpass_dai_startup,
268
.shutdown = q6apm_lpass_dai_shutdown,
269
.hw_params = q6hdmi_hw_params,
270
.set_fmt = q6i2s_set_fmt,
271
};
272
273
static const struct snd_soc_component_driver q6apm_lpass_dai_component = {
274
.name = "q6apm-be-dai-component",
275
.of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name,
276
.be_pcm_base = AUDIOREACH_BE_PCM_BASE,
277
.use_dai_pcm_id = true,
278
};
279
280
static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev)
281
{
282
struct q6dsp_audio_port_dai_driver_config cfg;
283
struct q6apm_lpass_dai_data *dai_data;
284
struct snd_soc_dai_driver *dais;
285
struct device *dev = &pdev->dev;
286
int num_dais;
287
288
dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL);
289
if (!dai_data)
290
return -ENOMEM;
291
292
dev_set_drvdata(dev, dai_data);
293
294
memset(&cfg, 0, sizeof(cfg));
295
cfg.q6i2s_ops = &q6i2s_ops;
296
cfg.q6dma_ops = &q6dma_ops;
297
cfg.q6hdmi_ops = &q6hdmi_ops;
298
dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais);
299
300
return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais);
301
}
302
303
#ifdef CONFIG_OF
304
static const struct of_device_id q6apm_lpass_dai_device_id[] = {
305
{ .compatible = "qcom,q6apm-lpass-dais" },
306
{},
307
};
308
MODULE_DEVICE_TABLE(of, q6apm_lpass_dai_device_id);
309
#endif
310
311
static struct platform_driver q6apm_lpass_dai_platform_driver = {
312
.driver = {
313
.name = "q6apm-lpass-dais",
314
.of_match_table = of_match_ptr(q6apm_lpass_dai_device_id),
315
},
316
.probe = q6apm_lpass_dai_dev_probe,
317
};
318
module_platform_driver(q6apm_lpass_dai_platform_driver);
319
320
MODULE_DESCRIPTION("AUDIOREACH APM LPASS dai driver");
321
MODULE_LICENSE("GPL");
322
323