/* * snd-dbmdx-pcm.c -- DBMDX ASoC platform driver * * Copyright (C) 2014 DSP Group * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #define DEBUG #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_OF #include #endif #include #include "dbmdx-interface.h" #define DRV_NAME "dbmdx-snd-soc-platform" /* defaults */ /* must be a multiple of 4 */ #define MAX_BUFFER_SIZE (131072*4) /* 3 seconds for each channel */ #define MIN_PERIOD_SIZE 4096 #define MAX_PERIOD_SIZE (MAX_BUFFER_SIZE / 64) #define USE_FORMATS (SNDRV_PCM_FMTBIT_S16_LE) #ifdef DBMDX_PCM_RATE_8000_SUPPORTED #define USE_RATE_MIN 8000 #else #define USE_RATE_MIN 16000 #endif #define USE_RATE_MAX 48000 #define USE_CHANNELS_MIN 1 #ifdef DBMDX_4CHANNELS_SUPPORT #define USE_CHANNELS_MAX 4 #else #define USE_CHANNELS_MAX 2 #endif #define USE_PERIODS_MIN 1 #define USE_PERIODS_MAX 1024 /* 3 seconds + 4 bytes for position */ #define REAL_BUFFER_SIZE (MAX_BUFFER_SIZE + 4) struct snd_dbmdx { struct snd_soc_card *card; struct snd_pcm_hardware pcm_hw; }; struct snd_dbmdx_runtime_data { struct snd_pcm_substream *substream; struct timer_list *timer; bool timer_is_active; struct delayed_work pcm_start_capture_work; struct delayed_work pcm_stop_capture_work; struct workqueue_struct *dbmdx_pcm_workq; unsigned int capture_in_progress; atomic_t command_in_progress; atomic_t number_of_cmds_in_progress; }; static struct snd_pcm_hardware dbmdx_pcm_hardware = { .info = (SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_INTERLEAVED | SNDRV_PCM_INFO_RESUME | SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH), .formats = USE_FORMATS, .rates = (SNDRV_PCM_RATE_16000 | #ifdef DBMDX_PCM_RATE_8000_SUPPORTED SNDRV_PCM_RATE_8000 | #endif #ifdef DBMDX_PCM_RATE_32000_SUPPORTED SNDRV_PCM_RATE_32000 | #endif #ifdef DBMDX_PCM_RATE_44100_SUPPORTED SNDRV_PCM_RATE_44100 | #endif SNDRV_PCM_RATE_48000), .rate_min = USE_RATE_MIN, .rate_max = USE_RATE_MAX, .channels_min = USE_CHANNELS_MIN, .channels_max = USE_CHANNELS_MAX, .buffer_bytes_max = MAX_BUFFER_SIZE, .period_bytes_min = MIN_PERIOD_SIZE, .period_bytes_max = MAX_PERIOD_SIZE, .periods_min = USE_PERIODS_MIN, .periods_max = USE_PERIODS_MAX, .fifo_size = 0, }; static DECLARE_WAIT_QUEUE_HEAD(dbmdx_wq); int pcm_command_in_progress(struct snd_dbmdx_runtime_data *prtd, bool is_command_in_progress) { if (is_command_in_progress) { if (!atomic_add_unless(&prtd->command_in_progress, 1, 1)) return -EBUSY; } else { atomic_set(&prtd->command_in_progress, 0); atomic_dec(&prtd->number_of_cmds_in_progress); wake_up_interruptible(&dbmdx_wq); } return 0; } void wait_for_pcm_commands(struct snd_dbmdx_runtime_data *prtd) { int ret; while (1) { wait_event_interruptible(dbmdx_wq, !(atomic_read(&prtd->command_in_progress))); ret = pcm_command_in_progress(prtd, 1); if (!ret) break; } } u32 stream_get_position(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; /* pr_debug("%s\n", __func__); */ if (runtime == NULL) { pr_err("%s: NULL ptr runtime\n", __func__); return 0; } return *(u32 *)&(runtime->dma_area[MAX_BUFFER_SIZE]); } void stream_set_position(struct snd_pcm_substream *substream, u32 position) { struct snd_pcm_runtime *runtime = substream->runtime; /* pr_debug("%s\n", __func__); */ if (runtime == NULL) { pr_err("%s: NULL ptr runtime\n", __func__); return; } *(u32 *)&(runtime->dma_area[MAX_BUFFER_SIZE]) = position; } static void dbmdx_pcm_timer(unsigned long _substream) { struct snd_pcm_substream *substream = (struct snd_pcm_substream *)_substream; struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dbmdx_runtime_data *prtd = runtime->private_data; struct timer_list *timer = prtd->timer; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec_dai->codec; unsigned int size = snd_pcm_lib_buffer_bytes(substream); u32 pos; unsigned long msecs; unsigned long to_copy; msecs = (runtime->period_size * 1000) / runtime->rate; mod_timer(timer, jiffies + msecs_to_jiffies(msecs)); /* pr_debug("%s\n", __func__); */ pos = stream_get_position(substream); to_copy = frames_to_bytes(runtime, runtime->period_size); if (dbmdx_get_samples(codec, runtime->dma_area + pos, runtime->channels * runtime->period_size)) { memset(runtime->dma_area + pos, 0, to_copy); pr_debug("%s Inserting %d bytes of silence\n", __func__, (int)to_copy); } pos += to_copy; if (pos >= size) pos = 0; stream_set_position(substream, pos); snd_pcm_period_elapsed(substream); } static int dbmdx_pcm_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params) { struct snd_pcm_runtime *runtime = substream->runtime; pr_debug("%s\n", __func__); snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer); runtime->channels = params_channels(hw_params); runtime->dma_bytes = params_buffer_bytes(hw_params); runtime->buffer_size = params_buffer_size(hw_params); runtime->rate = params_rate(hw_params); return 0; } static int dbmdx_pcm_prepare(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; size_t buf_bytes; size_t period_bytes; pr_debug("%s\n", __func__); memset(runtime->dma_area, 0, REAL_BUFFER_SIZE); buf_bytes = snd_pcm_lib_buffer_bytes(substream); period_bytes = snd_pcm_lib_period_bytes(substream); pr_debug("%s - buffer size =%d period size = %d\n", __func__, (int)buf_bytes, (int)period_bytes); /* We only support buffers that are multiples of the period */ if (buf_bytes % period_bytes) { pr_err("%s - buffer=%d not multiple of period=%d\n", __func__, (int)buf_bytes, (int)period_bytes); return -EINVAL; } return 0; } static int dbmdx_start_period_timer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dbmdx_runtime_data *prtd = runtime->private_data; struct timer_list *timer = prtd->timer; unsigned long msecs; pr_debug("%s\n", __func__); prtd->timer_is_active = true; *(u32 *)&(runtime->dma_area[MAX_BUFFER_SIZE]) = 0; msecs = (runtime->period_size * 500) / runtime->rate; mod_timer(timer, jiffies + msecs_to_jiffies(msecs)); return 0; } static int dbmdx_stop_period_timer(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dbmdx_runtime_data *prtd = runtime->private_data; struct timer_list *timer = prtd->timer; pr_debug("%s\n", __func__); del_timer_sync(timer); prtd->timer_is_active = false; return 0; } int dbmdx_set_pcm_timer_mode(struct snd_pcm_substream *substream, bool enable_timer) { int ret; struct snd_pcm_runtime *runtime; struct snd_dbmdx_runtime_data *prtd; if (!substream) { pr_debug("%s:Substream is NULL\n", __func__); return -EINVAL; } runtime = substream->runtime; if (!runtime) { pr_debug("%s:Runtime is NULL\n", __func__); return -EINVAL; } prtd = runtime->private_data; if (!prtd) { pr_debug("%s:Runtime Pr. Data is NULL\n", __func__); return -EINVAL; } if (enable_timer) { if (!(prtd->capture_in_progress)) { pr_debug("%s:Capture is not in progress\n", __func__); return -EINVAL; } if (prtd->timer_is_active) { pr_debug("%s:Timer is active\n", __func__); return 0; } ret = dbmdx_start_period_timer(substream); if (ret < 0) { pr_err("%s: failed to start capture device\n", __func__); return -EIO; } } else { if (!(prtd->timer_is_active)) { pr_debug("%s:Timer is not active\n", __func__); return 0; } ret = dbmdx_stop_period_timer(substream); if (ret < 0) { pr_err("%s: failed to stop capture device\n", __func__); return -EIO; } } return 0; } static void dbmdx_pcm_start_capture_work(struct work_struct *work) { int ret; struct snd_dbmdx_runtime_data *prtd = container_of( work, struct snd_dbmdx_runtime_data, pcm_start_capture_work.work); struct snd_pcm_substream *substream = prtd->substream; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec_dai->codec; pr_debug("%s:\n", __func__); wait_for_pcm_commands(prtd); if (prtd->capture_in_progress) { pr_debug("%s:Capture is already in progress\n", __func__); goto out; } prtd->capture_in_progress = 1; ret = dbmdx_start_pcm_streaming(codec, substream); if (ret < 0) { prtd->capture_in_progress = 0; pr_err("%s: failed to start capture device\n", __func__); goto out; } msleep(DBMDX_MSLEEP_PCM_STREAMING_WORK); out: pcm_command_in_progress(prtd, 0); } static void dbmdx_pcm_stop_capture_work(struct work_struct *work) { int ret; struct snd_dbmdx_runtime_data *prtd = container_of( work, struct snd_dbmdx_runtime_data, pcm_stop_capture_work.work); struct snd_pcm_substream *substream = prtd->substream; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec_dai->codec; pr_debug("%s:\n", __func__); wait_for_pcm_commands(prtd); if (!(prtd->capture_in_progress)) { pr_debug("%s:Capture is not in progress\n", __func__); goto out; } ret = dbmdx_stop_pcm_streaming(codec); if (ret < 0) pr_err("%s: failed to stop pcm streaming\n", __func__); if (prtd->timer_is_active) { ret = dbmdx_stop_period_timer(substream); if (ret < 0) pr_err("%s: failed to stop timer\n", __func__); } prtd->capture_in_progress = 0; out: pcm_command_in_progress(prtd, 0); } static int dbmdx_pcm_open(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec_dai->codec; struct snd_dbmdx_runtime_data *prtd; struct timer_list *timer; int ret; pr_debug("%s\n", __func__); if (dbmdx_codec_lock(codec)) { ret = -EBUSY; goto out; } prtd = kzalloc(sizeof(struct snd_dbmdx_runtime_data), GFP_KERNEL); if (prtd == NULL) { ret = -ENOMEM; goto out_unlock; } timer = kzalloc(sizeof(*timer), GFP_KERNEL); if (!timer) { ret = -ENOMEM; goto out_free_prtd; } init_timer(timer); timer->function = dbmdx_pcm_timer; timer->data = (unsigned long)substream; prtd->timer = timer; prtd->substream = substream; atomic_set(&prtd->command_in_progress, 0); atomic_set(&prtd->number_of_cmds_in_progress, 0); INIT_DELAYED_WORK(&prtd->pcm_start_capture_work, dbmdx_pcm_start_capture_work); INIT_DELAYED_WORK(&prtd->pcm_stop_capture_work, dbmdx_pcm_stop_capture_work); prtd->dbmdx_pcm_workq = create_workqueue("dbmdx-pcm-wq"); if (!prtd->dbmdx_pcm_workq) { pr_err("%s: Could not create pcm workqueue\n", __func__); ret = -EIO; goto out_free_timer; } runtime->private_data = prtd; snd_soc_set_runtime_hwparams(substream, &dbmdx_pcm_hardware); ret = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); if (ret < 0) pr_debug("%s Error setting pcm constraint int\n", __func__); return 0; out_free_timer: kfree(timer); out_free_prtd: kfree(prtd); out_unlock: dbmdx_codec_unlock(codec); out: return ret; } static int dbmdx_pcm_trigger(struct snd_pcm_substream *substream, int cmd) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dbmdx_runtime_data *prtd; int ret = 0; int num_of_active_cmds; pr_debug("%s: cmd=%d\n", __func__, cmd); if (runtime == NULL) { pr_err("%s: runtime NULL ptr\n", __func__); return -EFAULT; } prtd = runtime->private_data; if (prtd == NULL) { pr_err("%s: prtd NULL ptr\n", __func__); return -EFAULT; } num_of_active_cmds = atomic_read(&prtd->number_of_cmds_in_progress); pr_debug("%s: Number of active commands=%d\n", __func__, num_of_active_cmds); switch (cmd) { case SNDRV_PCM_TRIGGER_START: atomic_inc(&prtd->number_of_cmds_in_progress); ret = queue_delayed_work(prtd->dbmdx_pcm_workq, &prtd->pcm_start_capture_work, msecs_to_jiffies(num_of_active_cmds*100)); if (!ret) { pr_debug("%s: Start command is already pending\n", __func__); atomic_dec(&prtd->number_of_cmds_in_progress); } else pr_debug("%s: Start has been scheduled\n", __func__); break; case SNDRV_PCM_TRIGGER_RESUME: return 0; case SNDRV_PCM_TRIGGER_STOP: atomic_inc(&prtd->number_of_cmds_in_progress); ret = queue_delayed_work(prtd->dbmdx_pcm_workq, &prtd->pcm_stop_capture_work, msecs_to_jiffies(num_of_active_cmds*100)); if (!ret) { pr_debug("%s: Stop command is already pending\n", __func__); atomic_dec(&prtd->number_of_cmds_in_progress); } else pr_debug("%s: Stop has been scheduled\n", __func__); break; case SNDRV_PCM_TRIGGER_SUSPEND: return 0; } return ret; } static int dbmdx_pcm_close(struct snd_pcm_substream *substream) { struct snd_pcm_runtime *runtime = substream->runtime; struct snd_dbmdx_runtime_data *prtd = runtime->private_data; struct snd_soc_pcm_runtime *rtd = substream->private_data; struct snd_soc_codec *codec = rtd->codec_dai->codec; struct timer_list *timer = prtd->timer; pr_debug("%s\n", __func__); flush_delayed_work(&prtd->pcm_start_capture_work); flush_delayed_work(&prtd->pcm_stop_capture_work); queue_delayed_work(prtd->dbmdx_pcm_workq, &prtd->pcm_stop_capture_work, msecs_to_jiffies(0)); flush_delayed_work(&prtd->pcm_stop_capture_work); flush_workqueue(prtd->dbmdx_pcm_workq); usleep_range(10000, 11000); destroy_workqueue(prtd->dbmdx_pcm_workq); kfree(timer); kfree(prtd); timer = NULL; prtd = NULL; dbmdx_codec_unlock(codec); return 0; } static snd_pcm_uframes_t dbmdx_pcm_pointer(struct snd_pcm_substream *substream) { u32 pos; /* pr_debug("%s\n", __func__); */ pos = stream_get_position(substream); return bytes_to_frames(substream->runtime, pos); } static struct snd_pcm_ops dbmdx_pcm_ops = { .open = dbmdx_pcm_open, .close = dbmdx_pcm_close, .ioctl = snd_pcm_lib_ioctl, .hw_params = dbmdx_pcm_hw_params, .prepare = dbmdx_pcm_prepare, .trigger = dbmdx_pcm_trigger, .pointer = dbmdx_pcm_pointer, }; static int dbmdx_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream) { struct snd_pcm_substream *substream = pcm->streams[stream].substream; struct snd_dma_buffer *buf = &substream->dma_buffer; size_t size = MAX_BUFFER_SIZE; pr_debug("%s\n", __func__); buf->dev.type = SNDRV_DMA_TYPE_DEV; buf->dev.dev = pcm->card->dev; buf->private_data = NULL; buf->area = dma_alloc_coherent(pcm->card->dev, REAL_BUFFER_SIZE, &buf->addr, GFP_KERNEL); if (!buf->area) { pr_err("%s: Failed to allocate dma memory.\n", __func__); pr_err("%s: Please increase uncached DMA memory region\n", __func__); return -ENOMEM; } buf->bytes = size; return 0; } static int dbmdx_pcm_probe(struct snd_soc_platform *pt) { struct snd_dbmdx *dbmdx; pr_debug("%s\n", __func__); dbmdx = kzalloc(sizeof(*dbmdx), GFP_KERNEL); if (!dbmdx) return -ENOMEM; #if USE_ALSA_API_3_10_XX dbmdx->card = pt->card; #else dbmdx->card = pt->component.card; #endif dbmdx->pcm_hw = dbmdx_pcm_hardware; snd_soc_platform_set_drvdata(pt, dbmdx); return 0; } static int dbmdx_pcm_remove(struct snd_soc_platform *pt) { struct snd_dbmdx *dbmdx; pr_debug("%s\n", __func__); dbmdx = snd_soc_platform_get_drvdata(pt); kfree(dbmdx); return 0; } static int dbmdx_pcm_new(struct snd_soc_pcm_runtime *runtime) { struct snd_pcm *pcm; int ret = 0; pr_debug("%s\n", __func__); pcm = runtime->pcm; if (pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream) { ret = dbmdx_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_PLAYBACK); if (ret) goto out; } if (pcm->streams[SNDRV_PCM_STREAM_CAPTURE].substream) { ret = dbmdx_pcm_preallocate_dma_buffer(pcm, SNDRV_PCM_STREAM_CAPTURE); if (ret) goto out; } out: return ret; } static void dbmdx_pcm_free(struct snd_pcm *pcm) { struct snd_pcm_substream *substream; struct snd_dma_buffer *buf; int stream; pr_debug("%s\n", __func__); for (stream = 0; stream < 2; stream++) { substream = pcm->streams[stream].substream; if (!substream) continue; buf = &substream->dma_buffer; if (!buf->area) continue; dma_free_coherent(pcm->card->dev, REAL_BUFFER_SIZE, (void *)buf->area, buf->addr); buf->area = NULL; } } static struct snd_soc_platform_driver dbmdx_soc_platform = { .probe = &dbmdx_pcm_probe, .remove = &dbmdx_pcm_remove, .ops = &dbmdx_pcm_ops, .pcm_new = dbmdx_pcm_new, .pcm_free = dbmdx_pcm_free, }; static int dbmdx_pcm_platform_probe(struct platform_device *pdev) { int err; pr_debug("%s\n", __func__); err = snd_soc_register_platform(&pdev->dev, &dbmdx_soc_platform); if (err) dev_err(&pdev->dev, "%s: snd_soc_register_platform() failed", __func__); return err; } static int dbmdx_pcm_platform_remove(struct platform_device *pdev) { snd_soc_unregister_platform(&pdev->dev); pr_debug("%s\n", __func__); return 0; } static const struct of_device_id snd_soc_platform_of_ids[] = { { .compatible = "dspg,dbmdx-snd-soc-platform" }, { }, }; static struct platform_driver dbmdx_pcm_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, .of_match_table = snd_soc_platform_of_ids, }, .probe = dbmdx_pcm_platform_probe, .remove = dbmdx_pcm_platform_remove, }; #ifdef CONFIG_SND_SOC_DBMDX static int __init snd_dbmdx_pcm_init(void) { return platform_driver_register(&dbmdx_pcm_driver); } module_init(snd_dbmdx_pcm_init); static void __exit snd_dbmdx_pcm_exit(void) { platform_driver_unregister(&dbmdx_pcm_driver); } module_exit(snd_dbmdx_pcm_exit); #else int snd_dbmdx_pcm_init(void) { return platform_driver_register(&dbmdx_pcm_driver); } void snd_dbmdx_pcm_exit(void) { platform_driver_unregister(&dbmdx_pcm_driver); } #endif MODULE_DESCRIPTION("DBMDX ASoC platform driver"); MODULE_AUTHOR("DSP Group"); MODULE_LICENSE("GPL");