785 lines
19 KiB
C
785 lines
19 KiB
C
|
/*
|
||
|
* 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 <linux/workqueue.h>
|
||
|
#include <linux/clk.h>
|
||
|
#include <linux/interrupt.h>
|
||
|
#include <linux/io.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <linux/vmalloc.h>
|
||
|
#include <sound/pcm.h>
|
||
|
#include <sound/pcm_params.h>
|
||
|
#include <sound/soc.h>
|
||
|
#include <linux/kthread.h>
|
||
|
#include <linux/delay.h>
|
||
|
#if IS_ENABLED(CONFIG_OF)
|
||
|
#include <linux/of.h>
|
||
|
#endif
|
||
|
#include <linux/dma-mapping.h>
|
||
|
#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)
|
||
|
|
||
|
#if IS_ENABLED(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
|
||
|
#if IS_ENABLED(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)
|
||
|
|
||
|
u32 dma_bit_mask;
|
||
|
|
||
|
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 |
|
||
|
#if IS_ENABLED(DBMDX_PCM_RATE_8000_SUPPORTED)
|
||
|
SNDRV_PCM_RATE_8000 |
|
||
|
#endif
|
||
|
#if IS_ENABLED(DBMDX_PCM_RATE_32000_SUPPORTED)
|
||
|
SNDRV_PCM_RATE_32000 |
|
||
|
#endif
|
||
|
#if IS_ENABLED(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(struct timer_list *t)
|
||
|
{
|
||
|
struct snd_dbmdx_runtime_data *prtd = from_timer(prtd, t, timer);
|
||
|
struct snd_pcm_substream *substream = prtd->substream;
|
||
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
||
|
struct timer_list *timer = &(prtd->timer);
|
||
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
||
|
struct snd_soc_component *component = rtd->codec_dai->component;
|
||
|
|
||
|
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(component, 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_component *component = rtd->codec_dai->component;
|
||
|
|
||
|
|
||
|
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(component, 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_component *component = rtd->codec_dai->component;
|
||
|
|
||
|
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(component);
|
||
|
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_component *component = rtd->codec_dai->component;
|
||
|
struct snd_dbmdx_runtime_data *prtd;
|
||
|
struct timer_list *timer;
|
||
|
int ret;
|
||
|
|
||
|
pr_debug("%s\n", __func__);
|
||
|
|
||
|
if (dbmdx_component_lock(component)) {
|
||
|
ret = -EBUSY;
|
||
|
goto out;
|
||
|
}
|
||
|
|
||
|
prtd = kzalloc(sizeof(struct snd_dbmdx_runtime_data), GFP_KERNEL);
|
||
|
if (prtd == NULL) {
|
||
|
ret = -ENOMEM;
|
||
|
goto out_unlock;
|
||
|
}
|
||
|
|
||
|
timer = &(prtd->timer);
|
||
|
timer_setup(timer, dbmdx_pcm_timer, 0);
|
||
|
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_prtd;
|
||
|
}
|
||
|
|
||
|
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_prtd:
|
||
|
kfree(prtd);
|
||
|
out_unlock:
|
||
|
dbmdx_component_unlock(component);
|
||
|
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_component *component = rtd->codec_dai->component;
|
||
|
|
||
|
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(prtd);
|
||
|
prtd = NULL;
|
||
|
|
||
|
dbmdx_component_unlock(component);
|
||
|
|
||
|
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;
|
||
|
int ret;
|
||
|
|
||
|
pr_debug("%s\n", __func__);
|
||
|
|
||
|
|
||
|
buf->dev.type = SNDRV_DMA_TYPE_DEV;
|
||
|
buf->dev.dev = pcm->card->dev;
|
||
|
buf->private_data = NULL;
|
||
|
|
||
|
if (dma_bit_mask) {
|
||
|
pr_info("%s: Configuring DMA_BIT_MASK as %d\n",
|
||
|
__func__, dma_bit_mask);
|
||
|
ret = dma_coerce_mask_and_coherent(pcm->card->dev,
|
||
|
DMA_BIT_MASK(dma_bit_mask));
|
||
|
}
|
||
|
|
||
|
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_component *c)
|
||
|
{
|
||
|
struct snd_dbmdx *dbmdx;
|
||
|
|
||
|
pr_debug("%s\n", __func__);
|
||
|
|
||
|
|
||
|
dbmdx = kzalloc(sizeof(*dbmdx), GFP_KERNEL);
|
||
|
if (!dbmdx)
|
||
|
return -ENOMEM;
|
||
|
dbmdx->card = c->card;
|
||
|
dbmdx->pcm_hw = dbmdx_pcm_hardware;
|
||
|
snd_soc_component_set_drvdata(c, dbmdx);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void dbmdx_pcm_remove(struct snd_soc_component *c)
|
||
|
{
|
||
|
struct snd_dbmdx *dbmdx;
|
||
|
|
||
|
pr_debug("%s\n", __func__);
|
||
|
|
||
|
|
||
|
dbmdx = snd_soc_component_get_drvdata(c);
|
||
|
kfree(dbmdx);
|
||
|
}
|
||
|
|
||
|
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 const struct snd_soc_component_driver dbmdx_soc_component_drv = {
|
||
|
.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;
|
||
|
int ret;
|
||
|
struct device_node *np = pdev->dev.of_node;
|
||
|
|
||
|
pr_debug("%s\n", __func__);
|
||
|
|
||
|
ret = of_property_read_u32(np, "dma_bit_mask",
|
||
|
&dma_bit_mask);
|
||
|
if ((ret && ret != -EINVAL)) {
|
||
|
dev_info(&pdev->dev, "%s: invalid 'dma_bit_mask' using default as 0\n",
|
||
|
__func__);
|
||
|
dma_bit_mask = 0;
|
||
|
} else {
|
||
|
dev_info(&pdev->dev, "%s: 'dma_bit_mask' = %d\n",
|
||
|
__func__, dma_bit_mask);
|
||
|
}
|
||
|
|
||
|
err = snd_soc_register_component(&pdev->dev, &dbmdx_soc_component_drv,
|
||
|
NULL, 0);
|
||
|
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_component(&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,
|
||
|
};
|
||
|
|
||
|
#if !IS_MODULE(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");
|