1 // SPDX-License-Identifier: GPL-2.0 2 // Copyright (c) 2011-2017, The Linux Foundation. All rights reserved. 3 // Copyright (c) 2018, Linaro Limited 4 5 #include <linux/init.h> 6 #include <linux/err.h> 7 #include <linux/module.h> 8 #include <linux/platform_device.h> 9 #include <linux/slab.h> 10 #include <linux/component.h> 11 #include <sound/soc.h> 12 #include <sound/soc.h> 13 #include <sound/soc-dapm.h> 14 #include <sound/pcm.h> 15 #include <asm/dma.h> 16 #include <linux/dma-mapping.h> 17 #include <linux/of_device.h> 18 #include <sound/pcm_params.h> 19 #include "q6asm.h" 20 #include "q6routing.h" 21 #include "q6dsp-errno.h" 22 23 #define DRV_NAME "q6asm-fe-dai" 24 25 #define PLAYBACK_MIN_NUM_PERIODS 2 26 #define PLAYBACK_MAX_NUM_PERIODS 8 27 #define PLAYBACK_MAX_PERIOD_SIZE 65536 28 #define PLAYBACK_MIN_PERIOD_SIZE 128 29 #define CAPTURE_MIN_NUM_PERIODS 2 30 #define CAPTURE_MAX_NUM_PERIODS 8 31 #define CAPTURE_MAX_PERIOD_SIZE 4096 32 #define CAPTURE_MIN_PERIOD_SIZE 320 33 #define SID_MASK_DEFAULT 0xF 34 35 enum stream_state { 36 Q6ASM_STREAM_IDLE = 0, 37 Q6ASM_STREAM_STOPPED, 38 Q6ASM_STREAM_RUNNING, 39 }; 40 41 struct q6asm_dai_rtd { 42 struct snd_pcm_substream *substream; 43 phys_addr_t phys; 44 unsigned int pcm_size; 45 unsigned int pcm_count; 46 unsigned int pcm_irq_pos; /* IRQ position */ 47 unsigned int periods; 48 uint16_t bits_per_sample; 49 uint16_t source; /* Encoding source bit mask */ 50 struct audio_client *audio_client; 51 uint16_t session_id; 52 enum stream_state state; 53 }; 54 55 struct q6asm_dai_data { 56 long long int sid; 57 }; 58 59 static struct snd_pcm_hardware q6asm_dai_hardware_capture = { 60 .info = (SNDRV_PCM_INFO_MMAP | 61 SNDRV_PCM_INFO_BLOCK_TRANSFER | 62 SNDRV_PCM_INFO_MMAP_VALID | 63 SNDRV_PCM_INFO_INTERLEAVED | 64 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), 65 .formats = (SNDRV_PCM_FMTBIT_S16_LE | 66 SNDRV_PCM_FMTBIT_S24_LE), 67 .rates = SNDRV_PCM_RATE_8000_48000, 68 .rate_min = 8000, 69 .rate_max = 48000, 70 .channels_min = 1, 71 .channels_max = 4, 72 .buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * 73 CAPTURE_MAX_PERIOD_SIZE, 74 .period_bytes_min = CAPTURE_MIN_PERIOD_SIZE, 75 .period_bytes_max = CAPTURE_MAX_PERIOD_SIZE, 76 .periods_min = CAPTURE_MIN_NUM_PERIODS, 77 .periods_max = CAPTURE_MAX_NUM_PERIODS, 78 .fifo_size = 0, 79 }; 80 81 static struct snd_pcm_hardware q6asm_dai_hardware_playback = { 82 .info = (SNDRV_PCM_INFO_MMAP | 83 SNDRV_PCM_INFO_BLOCK_TRANSFER | 84 SNDRV_PCM_INFO_MMAP_VALID | 85 SNDRV_PCM_INFO_INTERLEAVED | 86 SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME), 87 .formats = (SNDRV_PCM_FMTBIT_S16_LE | 88 SNDRV_PCM_FMTBIT_S24_LE), 89 .rates = SNDRV_PCM_RATE_8000_192000, 90 .rate_min = 8000, 91 .rate_max = 192000, 92 .channels_min = 1, 93 .channels_max = 8, 94 .buffer_bytes_max = (PLAYBACK_MAX_NUM_PERIODS * 95 PLAYBACK_MAX_PERIOD_SIZE), 96 .period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE, 97 .period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE, 98 .periods_min = PLAYBACK_MIN_NUM_PERIODS, 99 .periods_max = PLAYBACK_MAX_NUM_PERIODS, 100 .fifo_size = 0, 101 }; 102 103 #define Q6ASM_FEDAI_DRIVER(num) { \ 104 .playback = { \ 105 .stream_name = "MultiMedia"#num" Playback", \ 106 .rates = (SNDRV_PCM_RATE_8000_192000| \ 107 SNDRV_PCM_RATE_KNOT), \ 108 .formats = (SNDRV_PCM_FMTBIT_S16_LE | \ 109 SNDRV_PCM_FMTBIT_S24_LE), \ 110 .channels_min = 1, \ 111 .channels_max = 8, \ 112 .rate_min = 8000, \ 113 .rate_max = 192000, \ 114 }, \ 115 .capture = { \ 116 .stream_name = "MultiMedia"#num" Capture", \ 117 .rates = (SNDRV_PCM_RATE_8000_48000| \ 118 SNDRV_PCM_RATE_KNOT), \ 119 .formats = (SNDRV_PCM_FMTBIT_S16_LE | \ 120 SNDRV_PCM_FMTBIT_S24_LE), \ 121 .channels_min = 1, \ 122 .channels_max = 4, \ 123 .rate_min = 8000, \ 124 .rate_max = 48000, \ 125 }, \ 126 .name = "MultiMedia"#num, \ 127 .probe = fe_dai_probe, \ 128 .id = MSM_FRONTEND_DAI_MULTIMEDIA##num, \ 129 } 130 131 /* Conventional and unconventional sample rate supported */ 132 static unsigned int supported_sample_rates[] = { 133 8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 134 88200, 96000, 176400, 192000 135 }; 136 137 static struct snd_pcm_hw_constraint_list constraints_sample_rates = { 138 .count = ARRAY_SIZE(supported_sample_rates), 139 .list = supported_sample_rates, 140 .mask = 0, 141 }; 142 143 static void event_handler(uint32_t opcode, uint32_t token, 144 uint32_t *payload, void *priv) 145 { 146 struct q6asm_dai_rtd *prtd = priv; 147 struct snd_pcm_substream *substream = prtd->substream; 148 149 switch (opcode) { 150 case ASM_CLIENT_EVENT_CMD_RUN_DONE: 151 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 152 q6asm_write_async(prtd->audio_client, 153 prtd->pcm_count, 0, 0, NO_TIMESTAMP); 154 break; 155 case ASM_CLIENT_EVENT_CMD_EOS_DONE: 156 prtd->state = Q6ASM_STREAM_STOPPED; 157 break; 158 case ASM_CLIENT_EVENT_DATA_WRITE_DONE: { 159 prtd->pcm_irq_pos += prtd->pcm_count; 160 snd_pcm_period_elapsed(substream); 161 if (prtd->state == Q6ASM_STREAM_RUNNING) 162 q6asm_write_async(prtd->audio_client, 163 prtd->pcm_count, 0, 0, NO_TIMESTAMP); 164 165 break; 166 } 167 case ASM_CLIENT_EVENT_DATA_READ_DONE: 168 prtd->pcm_irq_pos += prtd->pcm_count; 169 snd_pcm_period_elapsed(substream); 170 if (prtd->state == Q6ASM_STREAM_RUNNING) 171 q6asm_read(prtd->audio_client); 172 173 break; 174 default: 175 break; 176 } 177 } 178 179 static int q6asm_dai_prepare(struct snd_pcm_substream *substream) 180 { 181 struct snd_pcm_runtime *runtime = substream->runtime; 182 struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; 183 struct q6asm_dai_rtd *prtd = runtime->private_data; 184 struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME); 185 struct q6asm_dai_data *pdata; 186 int ret, i; 187 188 pdata = snd_soc_component_get_drvdata(c); 189 if (!pdata) 190 return -EINVAL; 191 192 if (!prtd || !prtd->audio_client) { 193 pr_err("%s: private data null or audio client freed\n", 194 __func__); 195 return -EINVAL; 196 } 197 198 prtd->pcm_count = snd_pcm_lib_period_bytes(substream); 199 prtd->pcm_irq_pos = 0; 200 /* rate and channels are sent to audio driver */ 201 if (prtd->state) { 202 /* clear the previous setup if any */ 203 q6asm_cmd(prtd->audio_client, CMD_CLOSE); 204 q6asm_unmap_memory_regions(substream->stream, 205 prtd->audio_client); 206 q6routing_stream_close(soc_prtd->dai_link->id, 207 substream->stream); 208 } 209 210 ret = q6asm_map_memory_regions(substream->stream, prtd->audio_client, 211 prtd->phys, 212 (prtd->pcm_size / prtd->periods), 213 prtd->periods); 214 215 if (ret < 0) { 216 pr_err("Audio Start: Buffer Allocation failed rc = %d\n", 217 ret); 218 return -ENOMEM; 219 } 220 221 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 222 ret = q6asm_open_write(prtd->audio_client, FORMAT_LINEAR_PCM, 223 prtd->bits_per_sample); 224 } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { 225 ret = q6asm_open_read(prtd->audio_client, FORMAT_LINEAR_PCM, 226 prtd->bits_per_sample); 227 } 228 229 if (ret < 0) { 230 pr_err("%s: q6asm_open_write failed\n", __func__); 231 q6asm_audio_client_free(prtd->audio_client); 232 prtd->audio_client = NULL; 233 return -ENOMEM; 234 } 235 236 prtd->session_id = q6asm_get_session_id(prtd->audio_client); 237 ret = q6routing_stream_open(soc_prtd->dai_link->id, LEGACY_PCM_MODE, 238 prtd->session_id, substream->stream); 239 if (ret) { 240 pr_err("%s: stream reg failed ret:%d\n", __func__, ret); 241 return ret; 242 } 243 244 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 245 ret = q6asm_media_format_block_multi_ch_pcm( 246 prtd->audio_client, runtime->rate, 247 runtime->channels, NULL, 248 prtd->bits_per_sample); 249 } else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) { 250 ret = q6asm_enc_cfg_blk_pcm_format_support(prtd->audio_client, 251 runtime->rate, runtime->channels, 252 prtd->bits_per_sample); 253 254 /* Queue the buffers */ 255 for (i = 0; i < runtime->periods; i++) 256 q6asm_read(prtd->audio_client); 257 258 } 259 if (ret < 0) 260 pr_info("%s: CMD Format block failed\n", __func__); 261 262 prtd->state = Q6ASM_STREAM_RUNNING; 263 264 return 0; 265 } 266 267 static int q6asm_dai_trigger(struct snd_pcm_substream *substream, int cmd) 268 { 269 int ret = 0; 270 struct snd_pcm_runtime *runtime = substream->runtime; 271 struct q6asm_dai_rtd *prtd = runtime->private_data; 272 273 switch (cmd) { 274 case SNDRV_PCM_TRIGGER_START: 275 case SNDRV_PCM_TRIGGER_RESUME: 276 case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: 277 ret = q6asm_run_nowait(prtd->audio_client, 0, 0, 0); 278 break; 279 case SNDRV_PCM_TRIGGER_STOP: 280 prtd->state = Q6ASM_STREAM_STOPPED; 281 ret = q6asm_cmd_nowait(prtd->audio_client, CMD_EOS); 282 break; 283 case SNDRV_PCM_TRIGGER_SUSPEND: 284 case SNDRV_PCM_TRIGGER_PAUSE_PUSH: 285 ret = q6asm_cmd_nowait(prtd->audio_client, CMD_PAUSE); 286 break; 287 default: 288 ret = -EINVAL; 289 break; 290 } 291 292 return ret; 293 } 294 295 static int q6asm_dai_open(struct snd_pcm_substream *substream) 296 { 297 struct snd_pcm_runtime *runtime = substream->runtime; 298 struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; 299 struct snd_soc_dai *cpu_dai = soc_prtd->cpu_dai; 300 struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME); 301 struct q6asm_dai_rtd *prtd; 302 struct q6asm_dai_data *pdata; 303 struct device *dev = c->dev; 304 int ret = 0; 305 int stream_id; 306 307 stream_id = cpu_dai->driver->id; 308 309 pdata = snd_soc_component_get_drvdata(c); 310 if (!pdata) { 311 pr_err("Drv data not found ..\n"); 312 return -EINVAL; 313 } 314 315 prtd = kzalloc(sizeof(struct q6asm_dai_rtd), GFP_KERNEL); 316 if (prtd == NULL) 317 return -ENOMEM; 318 319 prtd->substream = substream; 320 prtd->audio_client = q6asm_audio_client_alloc(dev, 321 (q6asm_cb)event_handler, prtd, stream_id, 322 LEGACY_PCM_MODE); 323 if (!prtd->audio_client) { 324 pr_info("%s: Could not allocate memory\n", __func__); 325 kfree(prtd); 326 return -ENOMEM; 327 } 328 329 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) 330 runtime->hw = q6asm_dai_hardware_playback; 331 else if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) 332 runtime->hw = q6asm_dai_hardware_capture; 333 334 ret = snd_pcm_hw_constraint_list(runtime, 0, 335 SNDRV_PCM_HW_PARAM_RATE, 336 &constraints_sample_rates); 337 if (ret < 0) 338 pr_info("snd_pcm_hw_constraint_list failed\n"); 339 /* Ensure that buffer size is a multiple of period size */ 340 ret = snd_pcm_hw_constraint_integer(runtime, 341 SNDRV_PCM_HW_PARAM_PERIODS); 342 if (ret < 0) 343 pr_info("snd_pcm_hw_constraint_integer failed\n"); 344 345 if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { 346 ret = snd_pcm_hw_constraint_minmax(runtime, 347 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 348 PLAYBACK_MIN_NUM_PERIODS * PLAYBACK_MIN_PERIOD_SIZE, 349 PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE); 350 if (ret < 0) { 351 pr_err("constraint for buffer bytes min max ret = %d\n", 352 ret); 353 } 354 } 355 356 ret = snd_pcm_hw_constraint_step(runtime, 0, 357 SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32); 358 if (ret < 0) { 359 pr_err("constraint for period bytes step ret = %d\n", 360 ret); 361 } 362 ret = snd_pcm_hw_constraint_step(runtime, 0, 363 SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32); 364 if (ret < 0) { 365 pr_err("constraint for buffer bytes step ret = %d\n", 366 ret); 367 } 368 369 runtime->private_data = prtd; 370 371 snd_soc_set_runtime_hwparams(substream, &q6asm_dai_hardware_playback); 372 373 runtime->dma_bytes = q6asm_dai_hardware_playback.buffer_bytes_max; 374 375 376 if (pdata->sid < 0) 377 prtd->phys = substream->dma_buffer.addr; 378 else 379 prtd->phys = substream->dma_buffer.addr | (pdata->sid << 32); 380 381 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); 382 383 return 0; 384 } 385 386 static int q6asm_dai_close(struct snd_pcm_substream *substream) 387 { 388 struct snd_pcm_runtime *runtime = substream->runtime; 389 struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; 390 struct q6asm_dai_rtd *prtd = runtime->private_data; 391 392 if (prtd->audio_client) { 393 q6asm_cmd(prtd->audio_client, CMD_CLOSE); 394 q6asm_unmap_memory_regions(substream->stream, 395 prtd->audio_client); 396 q6asm_audio_client_free(prtd->audio_client); 397 prtd->audio_client = NULL; 398 } 399 q6routing_stream_close(soc_prtd->dai_link->id, 400 substream->stream); 401 kfree(prtd); 402 return 0; 403 } 404 405 static snd_pcm_uframes_t q6asm_dai_pointer(struct snd_pcm_substream *substream) 406 { 407 408 struct snd_pcm_runtime *runtime = substream->runtime; 409 struct q6asm_dai_rtd *prtd = runtime->private_data; 410 411 if (prtd->pcm_irq_pos >= prtd->pcm_size) 412 prtd->pcm_irq_pos = 0; 413 414 return bytes_to_frames(runtime, (prtd->pcm_irq_pos)); 415 } 416 417 static int q6asm_dai_mmap(struct snd_pcm_substream *substream, 418 struct vm_area_struct *vma) 419 { 420 421 struct snd_pcm_runtime *runtime = substream->runtime; 422 struct snd_soc_pcm_runtime *soc_prtd = substream->private_data; 423 struct snd_soc_component *c = snd_soc_rtdcom_lookup(soc_prtd, DRV_NAME); 424 struct device *dev = c->dev; 425 426 return dma_mmap_coherent(dev, vma, 427 runtime->dma_area, runtime->dma_addr, 428 runtime->dma_bytes); 429 } 430 431 static int q6asm_dai_hw_params(struct snd_pcm_substream *substream, 432 struct snd_pcm_hw_params *params) 433 { 434 struct snd_pcm_runtime *runtime = substream->runtime; 435 struct q6asm_dai_rtd *prtd = runtime->private_data; 436 437 prtd->pcm_size = params_buffer_bytes(params); 438 prtd->periods = params_periods(params); 439 440 switch (params_format(params)) { 441 case SNDRV_PCM_FORMAT_S16_LE: 442 prtd->bits_per_sample = 16; 443 break; 444 case SNDRV_PCM_FORMAT_S24_LE: 445 prtd->bits_per_sample = 24; 446 break; 447 } 448 449 return 0; 450 } 451 452 static struct snd_pcm_ops q6asm_dai_ops = { 453 .open = q6asm_dai_open, 454 .hw_params = q6asm_dai_hw_params, 455 .close = q6asm_dai_close, 456 .ioctl = snd_pcm_lib_ioctl, 457 .prepare = q6asm_dai_prepare, 458 .trigger = q6asm_dai_trigger, 459 .pointer = q6asm_dai_pointer, 460 .mmap = q6asm_dai_mmap, 461 }; 462 463 static int q6asm_dai_pcm_new(struct snd_soc_pcm_runtime *rtd) 464 { 465 struct snd_pcm_substream *psubstream, *csubstream; 466 struct snd_soc_component *c = snd_soc_rtdcom_lookup(rtd, DRV_NAME); 467 struct snd_pcm *pcm = rtd->pcm; 468 struct device *dev; 469 int size, ret; 470 471 dev = c->dev; 472 size = q6asm_dai_hardware_playback.buffer_bytes_max; 473 psubstream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream; 474 if (psubstream) { 475 ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, 476 &psubstream->dma_buffer); 477 if (ret) { 478 dev_err(dev, "Cannot allocate buffer(s)\n"); 479 return ret; 480 } 481 } 482 483 csubstream = pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream; 484 if (csubstream) { 485 ret = snd_dma_alloc_pages(SNDRV_DMA_TYPE_DEV, dev, size, 486 &csubstream->dma_buffer); 487 if (ret) { 488 dev_err(dev, "Cannot allocate buffer(s)\n"); 489 if (psubstream) 490 snd_dma_free_pages(&psubstream->dma_buffer); 491 return ret; 492 } 493 } 494 495 return ret; 496 } 497 498 static void q6asm_dai_pcm_free(struct snd_pcm *pcm) 499 { 500 struct snd_pcm_substream *substream; 501 int i; 502 503 for (i = 0; i < ARRAY_SIZE(pcm->streams); i++) { 504 substream = pcm->streams[i].substream; 505 if (substream) { 506 snd_dma_free_pages(&substream->dma_buffer); 507 substream->dma_buffer.area = NULL; 508 substream->dma_buffer.addr = 0; 509 } 510 } 511 } 512 513 static const struct snd_soc_dapm_route afe_pcm_routes[] = { 514 {"MM_DL1", NULL, "MultiMedia1 Playback" }, 515 {"MM_DL2", NULL, "MultiMedia2 Playback" }, 516 {"MM_DL3", NULL, "MultiMedia3 Playback" }, 517 {"MM_DL4", NULL, "MultiMedia4 Playback" }, 518 {"MM_DL5", NULL, "MultiMedia5 Playback" }, 519 {"MM_DL6", NULL, "MultiMedia6 Playback" }, 520 {"MM_DL7", NULL, "MultiMedia7 Playback" }, 521 {"MM_DL7", NULL, "MultiMedia8 Playback" }, 522 {"MultiMedia1 Capture", NULL, "MM_UL1"}, 523 {"MultiMedia2 Capture", NULL, "MM_UL2"}, 524 {"MultiMedia3 Capture", NULL, "MM_UL3"}, 525 {"MultiMedia4 Capture", NULL, "MM_UL4"}, 526 {"MultiMedia5 Capture", NULL, "MM_UL5"}, 527 {"MultiMedia6 Capture", NULL, "MM_UL6"}, 528 {"MultiMedia7 Capture", NULL, "MM_UL7"}, 529 {"MultiMedia8 Capture", NULL, "MM_UL8"}, 530 531 }; 532 533 static int fe_dai_probe(struct snd_soc_dai *dai) 534 { 535 struct snd_soc_dapm_context *dapm; 536 537 dapm = snd_soc_component_get_dapm(dai->component); 538 snd_soc_dapm_add_routes(dapm, afe_pcm_routes, 539 ARRAY_SIZE(afe_pcm_routes)); 540 541 return 0; 542 } 543 544 545 static const struct snd_soc_component_driver q6asm_fe_dai_component = { 546 .name = DRV_NAME, 547 .ops = &q6asm_dai_ops, 548 .pcm_new = q6asm_dai_pcm_new, 549 .pcm_free = q6asm_dai_pcm_free, 550 551 }; 552 553 static struct snd_soc_dai_driver q6asm_fe_dais[] = { 554 Q6ASM_FEDAI_DRIVER(1), 555 Q6ASM_FEDAI_DRIVER(2), 556 Q6ASM_FEDAI_DRIVER(3), 557 Q6ASM_FEDAI_DRIVER(4), 558 Q6ASM_FEDAI_DRIVER(5), 559 Q6ASM_FEDAI_DRIVER(6), 560 Q6ASM_FEDAI_DRIVER(7), 561 Q6ASM_FEDAI_DRIVER(8), 562 }; 563 564 static int q6asm_dai_bind(struct device *dev, struct device *master, void *data) 565 { 566 struct device_node *node = dev->of_node; 567 struct of_phandle_args args; 568 struct q6asm_dai_data *pdata; 569 int rc; 570 571 pdata = kzalloc(sizeof(struct q6asm_dai_data), GFP_KERNEL); 572 if (!pdata) 573 return -ENOMEM; 574 575 rc = of_parse_phandle_with_fixed_args(node, "iommus", 1, 0, &args); 576 if (rc < 0) 577 pdata->sid = -1; 578 else 579 pdata->sid = args.args[0] & SID_MASK_DEFAULT; 580 581 dev_set_drvdata(dev, pdata); 582 583 return snd_soc_register_component(dev, &q6asm_fe_dai_component, 584 q6asm_fe_dais, 585 ARRAY_SIZE(q6asm_fe_dais)); 586 } 587 static void q6asm_dai_unbind(struct device *dev, struct device *master, 588 void *data) 589 { 590 struct q6asm_dai_data *pdata = dev_get_drvdata(dev); 591 592 snd_soc_unregister_component(dev); 593 594 kfree(pdata); 595 596 } 597 598 static const struct component_ops q6asm_dai_comp_ops = { 599 .bind = q6asm_dai_bind, 600 .unbind = q6asm_dai_unbind, 601 }; 602 603 static int q6asm_dai_probe(struct platform_device *pdev) 604 { 605 return component_add(&pdev->dev, &q6asm_dai_comp_ops); 606 } 607 608 static int q6asm_dai_dev_remove(struct platform_device *pdev) 609 { 610 component_del(&pdev->dev, &q6asm_dai_comp_ops); 611 return 0; 612 } 613 614 static struct platform_driver q6asm_dai_platform_driver = { 615 .driver = { 616 .name = "q6asm-dai", 617 }, 618 .probe = q6asm_dai_probe, 619 .remove = q6asm_dai_dev_remove, 620 }; 621 module_platform_driver(q6asm_dai_platform_driver); 622 623 MODULE_DESCRIPTION("Q6ASM dai driver"); 624 MODULE_LICENSE("GPL v2"); 625