blob: 58ae5fa6920f10f33f3e4ef1ab3b0e2672f621f1 [file] [log] [blame]
Meng Wang43bbb872018-12-10 12:32:05 +08001// SPDX-License-Identifier: GPL-2.0-only
Meng Wangac147b72017-10-30 16:46:16 +08002/* Copyright (c) 2013-2014, 2017-2018 The Linux Foundation. All rights reserved.
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +05303 */
4
5#include <linux/init.h>
6#include <linux/module.h>
7#include <linux/time.h>
8#include <linux/wait.h>
9#include <linux/platform_device.h>
10#include <linux/slab.h>
11#include <linux/dma-mapping.h>
12#include <sound/core.h>
13#include <sound/soc.h>
14#include <sound/pcm.h>
Laxminath Kasam605b42f2017-08-01 22:02:15 +053015#include <dsp/q6afe-v2.h>
16#include <dsp/q6voice.h>
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +053017
18#include "msm-pcm-q6-v2.h"
19#include "msm-pcm-routing-v2.h"
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +053020
Meng Wangee084a02018-09-04 16:11:58 +080021#define DRV_NAME "msm-pcm-dtmf-v2"
22
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +053023enum {
24 DTMF_IN_RX,
25 DTMF_IN_TX,
26};
27
28enum format {
29 FORMAT_S16_LE = 2
30};
31
32struct dtmf_det_info {
33 char session[MAX_SESSION_NAME_LEN];
34 uint8_t dir;
35 uint16_t high_freq;
36 uint16_t low_freq;
37};
38
39struct dtmf_buf_node {
40 struct list_head list;
41 struct dtmf_det_info dtmf_det_pkt;
42};
43
44enum dtmf_state {
45 DTMF_GEN_RX_STOPPED,
46 DTMF_GEN_RX_STARTED,
47};
48
49#define DTMF_MAX_Q_LEN 10
50#define DTMF_PKT_SIZE sizeof(struct dtmf_det_info)
51
52struct dtmf_drv_info {
53 enum dtmf_state state;
54 struct snd_pcm_substream *capture_substream;
55
56 struct list_head out_queue;
57 struct list_head free_out_queue;
58
59 wait_queue_head_t out_wait;
60
61 struct mutex lock;
62 spinlock_t dsp_lock;
63
64 uint8_t capture_start;
65 uint8_t capture_instance;
66
67 unsigned int pcm_capture_size;
68 unsigned int pcm_capture_count;
69 unsigned int pcm_capture_irq_pos;
70 unsigned int pcm_capture_buf_pos;
71};
72
73static struct snd_pcm_hardware msm_pcm_hardware = {
74 .info = (SNDRV_PCM_INFO_MMAP |
75 SNDRV_PCM_INFO_BLOCK_TRANSFER |
76 SNDRV_PCM_INFO_MMAP_VALID |
77 SNDRV_PCM_INFO_INTERLEAVED),
78 .formats = SNDRV_PCM_FMTBIT_S16_LE,
79 .channels_min = 1,
80 .channels_max = 1,
81 .buffer_bytes_max = (sizeof(struct dtmf_buf_node) * DTMF_MAX_Q_LEN),
82 .period_bytes_min = DTMF_PKT_SIZE,
83 .period_bytes_max = DTMF_PKT_SIZE,
84 .periods_min = DTMF_MAX_Q_LEN,
85 .periods_max = DTMF_MAX_Q_LEN,
86 .fifo_size = 0,
87};
88
89static int msm_dtmf_rx_generate_put(struct snd_kcontrol *kcontrol,
90 struct snd_ctl_elem_value *ucontrol)
91{
92 uint16_t low_freq = ucontrol->value.integer.value[0];
93 uint16_t high_freq = ucontrol->value.integer.value[1];
94 int64_t duration = ucontrol->value.integer.value[2];
95 uint16_t gain = ucontrol->value.integer.value[3];
96
97 pr_debug("%s: low_freq=%d high_freq=%d duration=%d gain=%d\n",
98 __func__, low_freq, high_freq, (int)duration, gain);
99 afe_dtmf_generate_rx(duration, high_freq, low_freq, gain);
100 return 0;
101}
102
103static int msm_dtmf_rx_generate_get(struct snd_kcontrol *kcontrol,
104 struct snd_ctl_elem_value *ucontrol)
105{
106 pr_debug("%s:\n", __func__);
107 ucontrol->value.integer.value[0] = 0;
108 return 0;
109}
110
111static int msm_dtmf_detect_voice_rx_put(struct snd_kcontrol *kcontrol,
112 struct snd_ctl_elem_value *ucontrol)
113{
114 int enable = ucontrol->value.integer.value[0];
115
116 pr_debug("%s: enable=%d\n", __func__, enable);
117 voc_enable_dtmf_rx_detection(voc_get_session_id(VOICE_SESSION_NAME),
118 enable);
119
120 return 0;
121}
122
123static int msm_dtmf_detect_voice_rx_get(struct snd_kcontrol *kcontrol,
124 struct snd_ctl_elem_value *ucontrol)
125{
126 ucontrol->value.integer.value[0] = 0;
127 return 0;
128}
129
130static int msm_dtmf_detect_volte_rx_put(struct snd_kcontrol *kcontrol,
131 struct snd_ctl_elem_value *ucontrol)
132{
133 int enable = ucontrol->value.integer.value[0];
134
135 pr_debug("%s: enable=%d\n", __func__, enable);
136 voc_enable_dtmf_rx_detection(voc_get_session_id(VOLTE_SESSION_NAME),
137 enable);
138
139 return 0;
140}
141
142static int msm_dtmf_detect_volte_rx_get(struct snd_kcontrol *kcontrol,
143 struct snd_ctl_elem_value *ucontrol)
144{
145 ucontrol->value.integer.value[0] = 0;
146 return 0;
147}
148
149static struct snd_kcontrol_new msm_dtmf_controls[] = {
150 SOC_SINGLE_MULTI_EXT("DTMF_Generate Rx Low High Duration Gain",
151 SND_SOC_NOPM, 0, 5000, 0, 4,
152 msm_dtmf_rx_generate_get,
153 msm_dtmf_rx_generate_put),
154 SOC_SINGLE_EXT("DTMF_Detect Rx Voice enable", SND_SOC_NOPM, 0, 1, 0,
155 msm_dtmf_detect_voice_rx_get,
156 msm_dtmf_detect_voice_rx_put),
157 SOC_SINGLE_EXT("DTMF_Detect Rx VoLTE enable", SND_SOC_NOPM, 0, 1, 0,
158 msm_dtmf_detect_volte_rx_get,
159 msm_dtmf_detect_volte_rx_put),
160};
161
Meng Wangee084a02018-09-04 16:11:58 +0800162static int msm_pcm_dtmf_probe(struct snd_soc_component *component)
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530163{
Meng Wangee084a02018-09-04 16:11:58 +0800164 snd_soc_add_component_controls(component, msm_dtmf_controls,
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530165 ARRAY_SIZE(msm_dtmf_controls));
166 return 0;
167}
168
169static void dtmf_rx_detected_cb(uint8_t *pkt,
170 char *session,
171 void *private_data)
172{
173 struct dtmf_buf_node *buf_node = NULL;
174 struct vss_istream_evt_rx_dtmf_detected *dtmf_det_pkt =
175 (struct vss_istream_evt_rx_dtmf_detected *)pkt;
176 struct dtmf_drv_info *prtd = private_data;
177 unsigned long dsp_flags;
178
179 pr_debug("%s\n", __func__);
180 if (prtd->capture_substream == NULL)
181 return;
182
183 /* Copy dtmf detected info into out_queue. */
184 spin_lock_irqsave(&prtd->dsp_lock, dsp_flags);
185 /* discarding dtmf detection info till start is received */
186 if (!list_empty(&prtd->free_out_queue) && prtd->capture_start) {
187 buf_node = list_first_entry(&prtd->free_out_queue,
188 struct dtmf_buf_node, list);
189 list_del(&buf_node->list);
190 buf_node->dtmf_det_pkt.high_freq = dtmf_det_pkt->high_freq;
191 buf_node->dtmf_det_pkt.low_freq = dtmf_det_pkt->low_freq;
192 if (session != NULL)
193 strlcpy(buf_node->dtmf_det_pkt.session,
194 session, MAX_SESSION_NAME_LEN);
195
196 buf_node->dtmf_det_pkt.dir = DTMF_IN_RX;
197 pr_debug("high =%d, low=%d session=%s\n",
198 buf_node->dtmf_det_pkt.high_freq,
199 buf_node->dtmf_det_pkt.low_freq,
200 buf_node->dtmf_det_pkt.session);
201 list_add_tail(&buf_node->list, &prtd->out_queue);
202 prtd->pcm_capture_irq_pos += prtd->pcm_capture_count;
203 spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags);
204 snd_pcm_period_elapsed(prtd->capture_substream);
205 } else {
206 spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags);
207 pr_err("DTMF detection pkt in Rx dropped, no free node available\n");
208 }
209
210 wake_up(&prtd->out_wait);
211}
212
213static int msm_pcm_capture_copy(struct snd_pcm_substream *substream,
Meng Wangac147b72017-10-30 16:46:16 +0800214 int channel, unsigned long hwoff,
215 void __user *buf, unsigned long fbytes)
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530216{
217 int ret = 0;
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530218 struct dtmf_buf_node *buf_node = NULL;
219 struct snd_pcm_runtime *runtime = substream->runtime;
220 struct dtmf_drv_info *prtd = runtime->private_data;
221 unsigned long dsp_flags;
222
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530223 ret = wait_event_interruptible_timeout(prtd->out_wait,
224 (!list_empty(&prtd->out_queue)),
225 1 * HZ);
226
227 if (ret > 0) {
Meng Wangac147b72017-10-30 16:46:16 +0800228 if (fbytes <= DTMF_PKT_SIZE) {
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530229 spin_lock_irqsave(&prtd->dsp_lock, dsp_flags);
230 buf_node = list_first_entry(&prtd->out_queue,
231 struct dtmf_buf_node, list);
232 list_del(&buf_node->list);
233 spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags);
234 ret = copy_to_user(buf,
235 &buf_node->dtmf_det_pkt,
Meng Wangac147b72017-10-30 16:46:16 +0800236 fbytes);
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530237 if (ret) {
238 pr_err("%s: Copy to user returned %d\n",
239 __func__, ret);
240 ret = -EFAULT;
241 }
242 spin_lock_irqsave(&prtd->dsp_lock, dsp_flags);
243 list_add_tail(&buf_node->list,
244 &prtd->free_out_queue);
245 spin_unlock_irqrestore(&prtd->dsp_lock, dsp_flags);
246
247 } else {
Meng Wangac147b72017-10-30 16:46:16 +0800248 pr_err("%s: Read count %lu > DTMF_PKT_SIZE\n",
249 __func__, fbytes);
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530250 ret = -ENOMEM;
251 }
252 } else if (ret == 0) {
253 pr_err("%s: No UL data available\n", __func__);
254 ret = -ETIMEDOUT;
255 } else {
256 pr_err("%s: Read was interrupted\n", __func__);
257 ret = -ERESTARTSYS;
258 }
259 return ret;
260}
261
262static int msm_pcm_copy(struct snd_pcm_substream *substream, int a,
Meng Wangac147b72017-10-30 16:46:16 +0800263 unsigned long hwoff, void __user *buf, unsigned long fbytes)
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530264{
265 int ret = 0;
266
267 pr_debug("%s() DTMF\n", __func__);
268
269 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
Meng Wangac147b72017-10-30 16:46:16 +0800270 ret = msm_pcm_capture_copy(substream, a, hwoff, buf, fbytes);
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530271
272 return ret;
273}
274
275static int msm_pcm_open(struct snd_pcm_substream *substream)
276{
277 struct snd_pcm_runtime *runtime = substream->runtime;
278 struct dtmf_drv_info *prtd = NULL;
279 int ret = 0;
280
281 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
282 prtd = kzalloc(sizeof(struct dtmf_drv_info), GFP_KERNEL);
283
284 if (prtd == NULL) {
285 ret = -ENOMEM;
286 goto done;
287 }
288
289 mutex_init(&prtd->lock);
290 spin_lock_init(&prtd->dsp_lock);
291 init_waitqueue_head(&prtd->out_wait);
292 INIT_LIST_HEAD(&prtd->out_queue);
293 INIT_LIST_HEAD(&prtd->free_out_queue);
294
295 runtime->hw = msm_pcm_hardware;
296
297 ret = snd_pcm_hw_constraint_integer(runtime,
298 SNDRV_PCM_HW_PARAM_PERIODS);
299 if (ret < 0)
300 pr_info("snd_pcm_hw_constraint_integer failed\n");
301
302 prtd->capture_substream = substream;
303 prtd->capture_instance++;
304 runtime->private_data = prtd;
305 }
306
307done:
308 return ret;
309}
310
311static int msm_pcm_close(struct snd_pcm_substream *substream)
312{
313 int ret = 0;
314 struct list_head *ptr = NULL;
315 struct list_head *next = NULL;
316 struct dtmf_buf_node *buf_node = NULL;
317 struct snd_dma_buffer *c_dma_buf;
318 struct snd_pcm_substream *c_substream;
319 struct snd_pcm_runtime *runtime = substream->runtime;
320 struct dtmf_drv_info *prtd = runtime->private_data;
321 unsigned long dsp_flags;
322
323 pr_debug("%s() DTMF\n", __func__);
324
325 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
326 mutex_lock(&prtd->lock);
327 wake_up(&prtd->out_wait);
328
329 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
330 prtd->capture_instance--;
331
332 if (!prtd->capture_instance) {
333 if (prtd->state == DTMF_GEN_RX_STARTED) {
334 prtd->state = DTMF_GEN_RX_STOPPED;
335 voc_disable_dtmf_det_on_active_sessions();
336 voc_register_dtmf_rx_detection_cb(NULL, NULL);
337 }
338 /* release all buffer */
339 /* release out_queue and free_out_queue */
340 pr_debug("release all buffer\n");
341 c_substream = prtd->capture_substream;
342 if (c_substream == NULL) {
343 pr_debug("c_substream is NULL\n");
344 mutex_unlock(&prtd->lock);
345 return -EINVAL;
346 }
347
348 c_dma_buf = &c_substream->dma_buffer;
349 if (c_dma_buf == NULL) {
350 pr_debug("c_dma_buf is NULL.\n");
351 mutex_unlock(&prtd->lock);
352 return -EINVAL;
353 }
354
355 if (c_dma_buf->area != NULL) {
356 spin_lock_irqsave(&prtd->dsp_lock, dsp_flags);
357 list_for_each_safe(ptr, next,
358 &prtd->out_queue) {
359 buf_node = list_entry(ptr,
360 struct dtmf_buf_node, list);
361 list_del(&buf_node->list);
362 }
363
364 list_for_each_safe(ptr, next,
365 &prtd->free_out_queue) {
366 buf_node = list_entry(ptr,
367 struct dtmf_buf_node, list);
368 list_del(&buf_node->list);
369 }
370
371 spin_unlock_irqrestore(&prtd->dsp_lock,
372 dsp_flags);
373 dma_free_coherent(c_substream->pcm->card->dev,
374 runtime->hw.buffer_bytes_max,
375 c_dma_buf->area,
376 c_dma_buf->addr);
377 c_dma_buf->area = NULL;
378 }
379 }
380 prtd->capture_substream = NULL;
381 mutex_unlock(&prtd->lock);
382 }
383
384 return ret;
385}
386
387static int msm_pcm_hw_params(struct snd_pcm_substream *substream,
388 struct snd_pcm_hw_params *params)
389{
390 struct snd_pcm_runtime *runtime = substream->runtime;
391 struct dtmf_drv_info *prtd = runtime->private_data;
392 struct snd_dma_buffer *dma_buf = &substream->dma_buffer;
393 struct dtmf_buf_node *buf_node = NULL;
394 int i = 0, offset = 0;
395 int ret = 0;
396
397 pr_debug("%s: DTMF\n", __func__);
398 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
399 mutex_lock(&prtd->lock);
400 dma_buf->dev.type = SNDRV_DMA_TYPE_DEV;
401 dma_buf->dev.dev = substream->pcm->card->dev;
402 dma_buf->private_data = NULL;
403
404 dma_buf->area = dma_alloc_coherent(substream->pcm->card->dev,
405 runtime->hw.buffer_bytes_max,
406 &dma_buf->addr, GFP_KERNEL);
407 if (!dma_buf->area) {
408 pr_err("%s:MSM DTMF dma_alloc failed\n", __func__);
409 mutex_unlock(&prtd->lock);
410 return -ENOMEM;
411 }
412
413 dma_buf->bytes = runtime->hw.buffer_bytes_max;
414 memset(dma_buf->area, 0, runtime->hw.buffer_bytes_max);
415
416 for (i = 0; i < DTMF_MAX_Q_LEN; i++) {
417 pr_debug("node =%d\n", i);
418 buf_node = (void *) dma_buf->area + offset;
419 list_add_tail(&buf_node->list,
420 &prtd->free_out_queue);
421 offset = offset + sizeof(struct dtmf_buf_node);
422 }
423
424 snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
425 mutex_unlock(&prtd->lock);
426 }
427
428 return ret;
429}
430
431static int msm_pcm_capture_prepare(struct snd_pcm_substream *substream)
432{
433 struct snd_pcm_runtime *runtime = substream->runtime;
434 struct dtmf_drv_info *prtd = runtime->private_data;
435
436 pr_debug("%s: DTMF\n", __func__);
437 prtd->pcm_capture_size = snd_pcm_lib_buffer_bytes(substream);
438 prtd->pcm_capture_count = snd_pcm_lib_period_bytes(substream);
439 prtd->pcm_capture_irq_pos = 0;
440 prtd->pcm_capture_buf_pos = 0;
441 return 0;
442}
443
444static int msm_pcm_prepare(struct snd_pcm_substream *substream)
445{
446 struct snd_pcm_runtime *runtime = substream->runtime;
447 struct dtmf_drv_info *prtd = runtime->private_data;
448
449 pr_debug("%s: DTMF\n", __func__);
450
451 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
452 mutex_lock(&prtd->lock);
453
454 msm_pcm_capture_prepare(substream);
455
456 if (runtime->format != FORMAT_S16_LE) {
457 pr_err("format:%u doesn't match %d\n",
458 (uint32_t)runtime->format, FORMAT_S16_LE);
459 mutex_unlock(&prtd->lock);
460 return -EINVAL;
461 }
462
463 if (prtd->capture_instance &&
464 (prtd->state != DTMF_GEN_RX_STARTED)) {
465 voc_register_dtmf_rx_detection_cb(dtmf_rx_detected_cb,
466 prtd);
467 prtd->state = DTMF_GEN_RX_STARTED;
468 }
469 mutex_unlock(&prtd->lock);
470 }
471
472 return 0;
473}
474
475static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
476{
477 int ret = 0;
478 struct snd_pcm_runtime *runtime = substream->runtime;
479 struct dtmf_drv_info *prtd = runtime->private_data;
480
481 switch (cmd) {
482 case SNDRV_PCM_TRIGGER_START:
483 case SNDRV_PCM_TRIGGER_RESUME:
484 pr_debug("%s: Trigger start\n", __func__);
485 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
486 prtd->capture_start = 1;
487 break;
488 case SNDRV_PCM_TRIGGER_STOP:
489 pr_debug("SNDRV_PCM_TRIGGER_STOP\n");
490 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
491 prtd->capture_start = 0;
492 break;
493 default:
494 ret = -EINVAL;
495 break;
496 }
497
498 return ret;
499}
500
501static snd_pcm_uframes_t msm_pcm_pointer(struct snd_pcm_substream *substream)
502{
503 snd_pcm_uframes_t ret = 0;
504 struct snd_pcm_runtime *runtime = substream->runtime;
505 struct dtmf_drv_info *prtd = runtime->private_data;
506
507 if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
508 if (prtd->pcm_capture_irq_pos >= prtd->pcm_capture_size)
509 prtd->pcm_capture_irq_pos = 0;
510 ret = bytes_to_frames(runtime, (prtd->pcm_capture_irq_pos));
511 }
512
513 return ret;
514}
515
516static const struct snd_pcm_ops msm_pcm_ops = {
517 .open = msm_pcm_open,
Meng Wangac147b72017-10-30 16:46:16 +0800518 .copy_user = msm_pcm_copy,
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530519 .hw_params = msm_pcm_hw_params,
520 .close = msm_pcm_close,
521 .prepare = msm_pcm_prepare,
522 .trigger = msm_pcm_trigger,
523 .pointer = msm_pcm_pointer,
524};
525
526static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd)
527{
528 struct snd_card *card = rtd->card->snd_card;
529 int ret = 0;
530
531 if (!card->dev->coherent_dma_mask)
532 card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
533 return ret;
534}
535
Meng Wangee084a02018-09-04 16:11:58 +0800536static struct snd_soc_component_driver msm_soc_component = {
537 .name = DRV_NAME,
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530538 .ops = &msm_pcm_ops,
539 .pcm_new = msm_asoc_pcm_new,
540 .probe = msm_pcm_dtmf_probe,
541};
542
543static int msm_pcm_probe(struct platform_device *pdev)
544{
545 pr_debug("%s: dev name %s\n", __func__, dev_name(&pdev->dev));
546
Meng Wangee084a02018-09-04 16:11:58 +0800547 return snd_soc_register_component(&pdev->dev,
548 &msm_soc_component, NULL, 0);
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530549}
550
551static int msm_pcm_remove(struct platform_device *pdev)
552{
Meng Wangee084a02018-09-04 16:11:58 +0800553 snd_soc_unregister_component(&pdev->dev);
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530554 return 0;
555}
556
557static const struct of_device_id msm_pcm_dtmf_dt_match[] = {
558 {.compatible = "qcom,msm-pcm-dtmf"},
559 {}
560};
561
562MODULE_DEVICE_TABLE(of, msm_pcm_dtmf_dt_match);
563
564
565static struct platform_driver msm_pcm_driver = {
566 .driver = {
567 .name = "msm-pcm-dtmf",
568 .owner = THIS_MODULE,
569 .of_match_table = msm_pcm_dtmf_dt_match,
570 },
571 .probe = msm_pcm_probe,
572 .remove = msm_pcm_remove,
573};
574
Laxminath Kasam8b1366a2017-10-05 01:44:16 +0530575int __init msm_pcm_dtmf_init(void)
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530576{
577 return platform_driver_register(&msm_pcm_driver);
578}
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530579
Asish Bhattacharya5faacb32017-12-04 17:23:15 +0530580void msm_pcm_dtmf_exit(void)
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530581{
582 platform_driver_unregister(&msm_pcm_driver);
583}
Asish Bhattacharya8e2277f2017-07-20 18:31:55 +0530584
585MODULE_DESCRIPTION("DTMF platform driver");
586MODULE_LICENSE("GPL v2");