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