1 // SPDX-License-Identifier: GPL-2.0
2 // Copyright (c) 2021, Linaro Limited
4 #include <dt-bindings/sound/qcom,q6dsp-lpass-ports.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"
19 #define AUDIOREACH_BE_PCM_BASE 16
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
];
27 static int q6dma_set_channel_map(struct snd_soc_dai
*dai
,
29 const unsigned int *tx_ch_mask
,
31 const unsigned int *rx_ch_mask
)
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
];
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
:
52 dev_err(dai
->dev
, "tx slot not found\n");
56 if (tx_num
> AR_PCM_MAX_NUM_CHANNEL
) {
57 dev_err(dai
->dev
, "invalid tx num %d\n",
61 for (i
= 0; i
< tx_num
; i
++)
62 cfg
->channel_map
[i
] = tx_ch_mask
[i
];
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
:
77 dev_err(dai
->dev
, "rx slot not found\n");
80 if (rx_num
> APM_PORT_MAX_AUDIO_CHAN_CNT
) {
81 dev_err(dai
->dev
, "invalid rx num %d\n",
85 for (i
= 0; i
< rx_num
; i
++)
86 cfg
->channel_map
[i
] = rx_ch_mask
[i
];
90 dev_err(dai
->dev
, "%s: invalid dai id 0x%x\n",
98 static int q6hdmi_hw_params(struct snd_pcm_substream
*substream
,
99 struct snd_pcm_hw_params
*params
, struct snd_soc_dai
*dai
)
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
;
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
);
112 case DISPLAY_PORT_RX_0
:
115 case DISPLAY_PORT_RX_1
... DISPLAY_PORT_RX_7
:
116 cfg
->dp_idx
= dai
->id
- DISPLAY_PORT_RX_1
+ 1;
120 ret
= q6dsp_get_channel_allocation(channels
);
124 cfg
->channel_allocation
= ret
;
129 static int q6dma_hw_params(struct snd_pcm_substream
*substream
,
130 struct snd_pcm_hw_params
*params
, struct snd_soc_dai
*dai
)
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
;
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
);
144 static void q6apm_lpass_dai_shutdown(struct snd_pcm_substream
*substream
, struct snd_soc_dai
*dai
)
146 struct q6apm_lpass_dai_data
*dai_data
= dev_get_drvdata(dai
->dev
);
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;
153 dev_err(dai
->dev
, "fail to close APM port (%d)\n", rc
);
156 if (dai_data
->graph
[dai
->id
]) {
157 q6apm_graph_close(dai_data
->graph
[dai
->id
]);
158 dai_data
->graph
[dai
->id
] = NULL
;
162 static int q6apm_lpass_dai_prepare(struct snd_pcm_substream
*substream
, struct snd_soc_dai
*dai
)
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
;
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;
174 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
) {
175 q6apm_graph_close(dai_data
->graph
[dai
->id
]);
176 dai_data
->graph
[dai
->id
] = NULL
;
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
184 if (substream
->stream
== SNDRV_PCM_STREAM_PLAYBACK
) {
185 graph
= q6apm_graph_open(dai
->dev
, NULL
, dai
->dev
, graph_id
);
187 dev_err(dai
->dev
, "Failed to open graph (%d)\n", graph_id
);
191 dai_data
->graph
[graph_id
] = graph
;
194 cfg
->direction
= substream
->stream
;
195 rc
= q6apm_graph_media_format_pcm(dai_data
->graph
[dai
->id
], cfg
);
197 dev_err(dai
->dev
, "Failed to set media format %d\n", rc
);
201 rc
= q6apm_graph_prepare(dai_data
->graph
[dai
->id
]);
203 dev_err(dai
->dev
, "Failed to prepare Graph %d\n", rc
);
207 rc
= q6apm_graph_start(dai_data
->graph
[dai
->id
]);
209 dev_err(dai
->dev
, "fail to start APM port %x\n", dai
->id
);
212 dai_data
->is_port_started
[dai
->id
] = true;
216 q6apm_graph_close(dai_data
->graph
[dai
->id
]);
217 dai_data
->graph
[dai
->id
] = NULL
;
221 static int q6apm_lpass_dai_startup(struct snd_pcm_substream
*substream
, struct snd_soc_dai
*dai
)
223 struct q6apm_lpass_dai_data
*dai_data
= dev_get_drvdata(dai
->dev
);
224 struct q6apm_graph
*graph
;
225 int graph_id
= dai
->id
;
227 if (substream
->stream
== SNDRV_PCM_STREAM_CAPTURE
) {
228 graph
= q6apm_graph_open(dai
->dev
, NULL
, dai
->dev
, graph_id
);
230 dev_err(dai
->dev
, "Failed to open graph (%d)\n", graph_id
);
231 return PTR_ERR(graph
);
233 dai_data
->graph
[graph_id
] = graph
;
239 static int q6i2s_set_fmt(struct snd_soc_dai
*dai
, unsigned int fmt
)
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
];
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
,
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
,
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
,
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,
280 static int q6apm_lpass_dai_dev_probe(struct platform_device
*pdev
)
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
;
288 dai_data
= devm_kzalloc(dev
, sizeof(*dai_data
), GFP_KERNEL
);
292 dev_set_drvdata(dev
, dai_data
);
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
);
300 return devm_snd_soc_register_component(dev
, &q6apm_lpass_dai_component
, dais
, num_dais
);
304 static const struct of_device_id q6apm_lpass_dai_device_id
[] = {
305 { .compatible
= "qcom,q6apm-lpass-dais" },
308 MODULE_DEVICE_TABLE(of
, q6apm_lpass_dai_device_id
);
311 static struct platform_driver q6apm_lpass_dai_platform_driver
= {
313 .name
= "q6apm-lpass-dais",
314 .of_match_table
= of_match_ptr(q6apm_lpass_dai_device_id
),
316 .probe
= q6apm_lpass_dai_dev_probe
,
318 module_platform_driver(q6apm_lpass_dai_platform_driver
);
320 MODULE_DESCRIPTION("AUDIOREACH APM LPASS dai driver");
321 MODULE_LICENSE("GPL");