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 return; 145 rc = q6apm_graph_stop(dai_data->graph[dai->id]); 146 if (rc < 0) 147 dev_err(dai->dev, "fail to close APM port (%d)\n", rc); 148 149 q6apm_graph_close(dai_data->graph[dai->id]); 150 dai_data->is_port_started[dai->id] = false; 151 } 152 153 static int q6apm_lpass_dai_prepare(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) 154 { 155 struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); 156 struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; 157 struct q6apm_graph *graph; 158 int graph_id = dai->id; 159 int rc; 160 161 if (dai_data->is_port_started[dai->id]) { 162 q6apm_graph_stop(dai_data->graph[dai->id]); 163 dai_data->is_port_started[dai->id] = false; 164 165 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 166 q6apm_graph_close(dai_data->graph[dai->id]); 167 } 168 169 /** 170 * It is recommend to load DSP with source graph first and then sink 171 * graph, so sequence for playback and capture will be different 172 */ 173 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 174 graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id); 175 if (IS_ERR(graph)) { 176 dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); 177 rc = PTR_ERR(graph); 178 return rc; 179 } 180 dai_data->graph[graph_id] = graph; 181 } 182 183 cfg->direction = substream->stream; 184 rc = q6apm_graph_media_format_pcm(dai_data->graph[dai->id], cfg); 185 186 if (rc) { 187 dev_err(dai->dev, "Failed to set media format %d\n", rc); 188 return rc; 189 } 190 191 rc = q6apm_graph_prepare(dai_data->graph[dai->id]); 192 if (rc) { 193 dev_err(dai->dev, "Failed to prepare Graph %d\n", rc); 194 return rc; 195 } 196 197 rc = q6apm_graph_start(dai_data->graph[dai->id]); 198 if (rc < 0) { 199 dev_err(dai->dev, "fail to start APM port %x\n", dai->id); 200 return rc; 201 } 202 dai_data->is_port_started[dai->id] = true; 203 204 return 0; 205 } 206 207 static int q6apm_lpass_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) 208 { 209 struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); 210 struct q6apm_graph *graph; 211 int graph_id = dai->id; 212 213 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { 214 graph = q6apm_graph_open(dai->dev, NULL, dai->dev, graph_id); 215 if (IS_ERR(graph)) { 216 dev_err(dai->dev, "Failed to open graph (%d)\n", graph_id); 217 return PTR_ERR(graph); 218 } 219 dai_data->graph[graph_id] = graph; 220 } 221 222 return 0; 223 } 224 225 static int q6i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt) 226 { 227 struct q6apm_lpass_dai_data *dai_data = dev_get_drvdata(dai->dev); 228 struct audioreach_module_config *cfg = &dai_data->module_config[dai->id]; 229 230 cfg->fmt = fmt; 231 232 return 0; 233 } 234 235 static const struct snd_soc_dai_ops q6dma_ops = { 236 .prepare = q6apm_lpass_dai_prepare, 237 .startup = q6apm_lpass_dai_startup, 238 .shutdown = q6apm_lpass_dai_shutdown, 239 .set_channel_map = q6dma_set_channel_map, 240 .hw_params = q6dma_hw_params, 241 }; 242 243 static const struct snd_soc_dai_ops q6i2s_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 q6hdmi_ops = { 252 .prepare = q6apm_lpass_dai_prepare, 253 .startup = q6apm_lpass_dai_startup, 254 .shutdown = q6apm_lpass_dai_shutdown, 255 .hw_params = q6hdmi_hw_params, 256 .set_fmt = q6i2s_set_fmt, 257 }; 258 259 static const struct snd_soc_component_driver q6apm_lpass_dai_component = { 260 .name = "q6apm-be-dai-component", 261 .of_xlate_dai_name = q6dsp_audio_ports_of_xlate_dai_name, 262 .be_pcm_base = AUDIOREACH_BE_PCM_BASE, 263 .use_dai_pcm_id = true, 264 }; 265 266 static int q6apm_lpass_dai_dev_probe(struct platform_device *pdev) 267 { 268 struct q6dsp_audio_port_dai_driver_config cfg; 269 struct q6apm_lpass_dai_data *dai_data; 270 struct snd_soc_dai_driver *dais; 271 struct device *dev = &pdev->dev; 272 int num_dais; 273 274 dai_data = devm_kzalloc(dev, sizeof(*dai_data), GFP_KERNEL); 275 if (!dai_data) 276 return -ENOMEM; 277 278 dev_set_drvdata(dev, dai_data); 279 280 memset(&cfg, 0, sizeof(cfg)); 281 cfg.q6i2s_ops = &q6i2s_ops; 282 cfg.q6dma_ops = &q6dma_ops; 283 cfg.q6hdmi_ops = &q6hdmi_ops; 284 dais = q6dsp_audio_ports_set_config(dev, &cfg, &num_dais); 285 286 return devm_snd_soc_register_component(dev, &q6apm_lpass_dai_component, dais, num_dais); 287 } 288 289 #ifdef CONFIG_OF 290 static const struct of_device_id q6apm_lpass_dai_device_id[] = { 291 { .compatible = "qcom,q6apm-lpass-dais" }, 292 {}, 293 }; 294 MODULE_DEVICE_TABLE(of, q6apm_lpass_dai_device_id); 295 #endif 296 297 static struct platform_driver q6apm_lpass_dai_platform_driver = { 298 .driver = { 299 .name = "q6apm-lpass-dais", 300 .of_match_table = of_match_ptr(q6apm_lpass_dai_device_id), 301 }, 302 .probe = q6apm_lpass_dai_dev_probe, 303 }; 304 module_platform_driver(q6apm_lpass_dai_platform_driver); 305 306 MODULE_DESCRIPTION("AUDIOREACH APM LPASS dai driver"); 307 MODULE_LICENSE("GPL"); 308