mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-27 11:19:37 +00:00
![Shengjiu Wang](/assets/img/avatar_default.png)
The rpmsg pcm device is a device which should support double buffering. Found this issue with pipewire. When there is no SNDRV_PCM_INFO_BATCH flag in driver, the pipewire will set headroom to be zero, and because rpmsg pcm device don't support residue report, when the latency setting is small, the "delay" always larger than "target" in alsa-pcm.c, that reading next period data is not scheduled on time. With SNDRV_PCM_INFO_BATCH flag in driver, the pipewire will select a smaller period size for device, then the task of reading next period data will be scheduled on time. Signed-off-by: Shengjiu Wang <shengjiu.wang@nxp.com> Link: https://lore.kernel.org/r/1694414287-13291-1-git-send-email-shengjiu.wang@nxp.com Signed-off-by: Mark Brown <broonie@kernel.org>
838 lines
23 KiB
C
838 lines
23 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
// Copyright 2017-2021 NXP
|
|
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/module.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/rpmsg.h>
|
|
#include <sound/core.h>
|
|
#include <sound/pcm.h>
|
|
#include <sound/pcm_params.h>
|
|
#include <sound/dmaengine_pcm.h>
|
|
#include <sound/soc.h>
|
|
|
|
#include "imx-pcm.h"
|
|
#include "fsl_rpmsg.h"
|
|
#include "imx-pcm-rpmsg.h"
|
|
|
|
static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = {
|
|
.info = SNDRV_PCM_INFO_INTERLEAVED |
|
|
SNDRV_PCM_INFO_BLOCK_TRANSFER |
|
|
SNDRV_PCM_INFO_BATCH |
|
|
SNDRV_PCM_INFO_MMAP |
|
|
SNDRV_PCM_INFO_MMAP_VALID |
|
|
SNDRV_PCM_INFO_NO_PERIOD_WAKEUP |
|
|
SNDRV_PCM_INFO_PAUSE |
|
|
SNDRV_PCM_INFO_RESUME,
|
|
.buffer_bytes_max = IMX_DEFAULT_DMABUF_SIZE,
|
|
.period_bytes_min = 512,
|
|
.period_bytes_max = 65536,
|
|
.periods_min = 2,
|
|
.periods_max = 6000,
|
|
.fifo_size = 0,
|
|
};
|
|
|
|
static int imx_rpmsg_pcm_send_message(struct rpmsg_msg *msg,
|
|
struct rpmsg_info *info)
|
|
{
|
|
struct rpmsg_device *rpdev = info->rpdev;
|
|
int ret = 0;
|
|
|
|
mutex_lock(&info->msg_lock);
|
|
if (!rpdev) {
|
|
dev_err(info->dev, "rpmsg channel not ready\n");
|
|
mutex_unlock(&info->msg_lock);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(&rpdev->dev, "send cmd %d\n", msg->s_msg.header.cmd);
|
|
|
|
if (!(msg->s_msg.header.type == MSG_TYPE_C))
|
|
reinit_completion(&info->cmd_complete);
|
|
|
|
ret = rpmsg_send(rpdev->ept, (void *)&msg->s_msg,
|
|
sizeof(struct rpmsg_s_msg));
|
|
if (ret) {
|
|
dev_err(&rpdev->dev, "rpmsg_send failed: %d\n", ret);
|
|
mutex_unlock(&info->msg_lock);
|
|
return ret;
|
|
}
|
|
|
|
/* No receive msg for TYPE_C command */
|
|
if (msg->s_msg.header.type == MSG_TYPE_C) {
|
|
mutex_unlock(&info->msg_lock);
|
|
return 0;
|
|
}
|
|
|
|
/* wait response from rpmsg */
|
|
ret = wait_for_completion_timeout(&info->cmd_complete,
|
|
msecs_to_jiffies(RPMSG_TIMEOUT));
|
|
if (!ret) {
|
|
dev_err(&rpdev->dev, "rpmsg_send cmd %d timeout!\n",
|
|
msg->s_msg.header.cmd);
|
|
mutex_unlock(&info->msg_lock);
|
|
return -ETIMEDOUT;
|
|
}
|
|
|
|
memcpy(&msg->r_msg, &info->r_msg, sizeof(struct rpmsg_r_msg));
|
|
memcpy(&info->msg[msg->r_msg.header.cmd].r_msg,
|
|
&msg->r_msg, sizeof(struct rpmsg_r_msg));
|
|
|
|
/*
|
|
* Reset the buffer pointer to be zero, actully we have
|
|
* set the buffer pointer to be zero in imx_rpmsg_terminate_all
|
|
* But if there is timer task queued in queue, after it is
|
|
* executed the buffer pointer will be changed, so need to
|
|
* reset it again with TERMINATE command.
|
|
*/
|
|
switch (msg->s_msg.header.cmd) {
|
|
case TX_TERMINATE:
|
|
info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
|
|
break;
|
|
case RX_TERMINATE:
|
|
info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
dev_dbg(&rpdev->dev, "cmd:%d, resp %d\n", msg->s_msg.header.cmd,
|
|
info->r_msg.param.resp);
|
|
|
|
mutex_unlock(&info->msg_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int imx_rpmsg_insert_workqueue(struct snd_pcm_substream *substream,
|
|
struct rpmsg_msg *msg,
|
|
struct rpmsg_info *info)
|
|
{
|
|
unsigned long flags;
|
|
int ret = 0;
|
|
|
|
/*
|
|
* Queue the work to workqueue.
|
|
* If the queue is full, drop the message.
|
|
*/
|
|
spin_lock_irqsave(&info->wq_lock, flags);
|
|
if (info->work_write_index != info->work_read_index) {
|
|
int index = info->work_write_index;
|
|
|
|
memcpy(&info->work_list[index].msg, msg,
|
|
sizeof(struct rpmsg_s_msg));
|
|
|
|
queue_work(info->rpmsg_wq, &info->work_list[index].work);
|
|
info->work_write_index++;
|
|
info->work_write_index %= WORK_MAX_NUM;
|
|
} else {
|
|
info->msg_drop_count[substream->stream]++;
|
|
ret = -EPIPE;
|
|
}
|
|
spin_unlock_irqrestore(&info->wq_lock, flags);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_hw_params(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream,
|
|
struct snd_pcm_hw_params *params)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct rpmsg_msg *msg;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_HW_PARAM];
|
|
msg->s_msg.header.cmd = TX_HW_PARAM;
|
|
} else {
|
|
msg = &info->msg[RX_HW_PARAM];
|
|
msg->s_msg.header.cmd = RX_HW_PARAM;
|
|
}
|
|
|
|
msg->s_msg.param.rate = params_rate(params);
|
|
|
|
switch (params_format(params)) {
|
|
case SNDRV_PCM_FORMAT_S16_LE:
|
|
msg->s_msg.param.format = RPMSG_S16_LE;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_S24_LE:
|
|
msg->s_msg.param.format = RPMSG_S24_LE;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_DSD_U16_LE:
|
|
msg->s_msg.param.format = RPMSG_DSD_U16_LE;
|
|
break;
|
|
case SNDRV_PCM_FORMAT_DSD_U32_LE:
|
|
msg->s_msg.param.format = RPMSG_DSD_U32_LE;
|
|
break;
|
|
default:
|
|
msg->s_msg.param.format = RPMSG_S32_LE;
|
|
break;
|
|
}
|
|
|
|
switch (params_channels(params)) {
|
|
case 1:
|
|
msg->s_msg.param.channels = RPMSG_CH_LEFT;
|
|
break;
|
|
case 2:
|
|
msg->s_msg.param.channels = RPMSG_CH_STEREO;
|
|
break;
|
|
default:
|
|
msg->s_msg.param.channels = params_channels(params);
|
|
break;
|
|
}
|
|
|
|
info->send_message(msg, info);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct rpmsg_msg *msg;
|
|
unsigned int pos = 0;
|
|
int buffer_tail = 0;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
|
|
else
|
|
msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
|
|
|
|
buffer_tail = msg->r_msg.param.buffer_tail;
|
|
pos = buffer_tail * snd_pcm_lib_period_bytes(substream);
|
|
|
|
return bytes_to_frames(substream->runtime, pos);
|
|
}
|
|
|
|
static void imx_rpmsg_timer_callback(struct timer_list *t)
|
|
{
|
|
struct stream_timer *stream_timer =
|
|
from_timer(stream_timer, t, timer);
|
|
struct snd_pcm_substream *substream = stream_timer->substream;
|
|
struct rpmsg_info *info = stream_timer->info;
|
|
struct rpmsg_msg *msg;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
|
|
msg->s_msg.header.cmd = TX_PERIOD_DONE;
|
|
} else {
|
|
msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
|
|
msg->s_msg.header.cmd = RX_PERIOD_DONE;
|
|
}
|
|
|
|
imx_rpmsg_insert_workqueue(substream, msg, info);
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_open(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
|
|
struct snd_pcm_hardware pcm_hardware;
|
|
struct rpmsg_msg *msg;
|
|
int ret = 0;
|
|
int cmd;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_OPEN];
|
|
msg->s_msg.header.cmd = TX_OPEN;
|
|
|
|
/* reinitialize buffer counter*/
|
|
cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM;
|
|
info->msg[cmd].s_msg.param.buffer_tail = 0;
|
|
info->msg[cmd].r_msg.param.buffer_tail = 0;
|
|
info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
|
|
|
|
} else {
|
|
msg = &info->msg[RX_OPEN];
|
|
msg->s_msg.header.cmd = RX_OPEN;
|
|
|
|
/* reinitialize buffer counter*/
|
|
cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM;
|
|
info->msg[cmd].s_msg.param.buffer_tail = 0;
|
|
info->msg[cmd].r_msg.param.buffer_tail = 0;
|
|
info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
|
|
}
|
|
|
|
info->send_message(msg, info);
|
|
|
|
pcm_hardware = imx_rpmsg_pcm_hardware;
|
|
pcm_hardware.buffer_bytes_max = rpmsg->buffer_size;
|
|
pcm_hardware.period_bytes_max = pcm_hardware.buffer_bytes_max / 2;
|
|
|
|
snd_soc_set_runtime_hwparams(substream, &pcm_hardware);
|
|
|
|
ret = snd_pcm_hw_constraint_integer(substream->runtime,
|
|
SNDRV_PCM_HW_PARAM_PERIODS);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
info->msg_drop_count[substream->stream] = 0;
|
|
|
|
/* Create timer*/
|
|
info->stream_timer[substream->stream].info = info;
|
|
info->stream_timer[substream->stream].substream = substream;
|
|
timer_setup(&info->stream_timer[substream->stream].timer,
|
|
imx_rpmsg_timer_callback, 0);
|
|
return ret;
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_close(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_soc_pcm_runtime *rtd = asoc_substream_to_rtd(substream);
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct rpmsg_msg *msg;
|
|
|
|
/* Flush work in workqueue to make TX_CLOSE is the last message */
|
|
flush_workqueue(info->rpmsg_wq);
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_CLOSE];
|
|
msg->s_msg.header.cmd = TX_CLOSE;
|
|
} else {
|
|
msg = &info->msg[RX_CLOSE];
|
|
msg->s_msg.header.cmd = RX_CLOSE;
|
|
}
|
|
|
|
info->send_message(msg, info);
|
|
|
|
del_timer(&info->stream_timer[substream->stream].timer);
|
|
|
|
rtd->dai_link->ignore_suspend = 0;
|
|
|
|
if (info->msg_drop_count[substream->stream])
|
|
dev_warn(rtd->dev, "Msg is dropped!, number is %d\n",
|
|
info->msg_drop_count[substream->stream]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_prepare(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
|
|
|
|
/*
|
|
* NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts
|
|
* four conditions to determine the lpa is enabled.
|
|
*/
|
|
if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
|
|
runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) &&
|
|
rpmsg->enable_lpa) {
|
|
/*
|
|
* Ignore suspend operation in low power mode
|
|
* M core will continue playback music on A core suspend.
|
|
*/
|
|
rtd->dai_link->ignore_suspend = 1;
|
|
rpmsg->force_lpa = 1;
|
|
} else {
|
|
rpmsg->force_lpa = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void imx_rpmsg_pcm_dma_complete(void *arg)
|
|
{
|
|
struct snd_pcm_substream *substream = arg;
|
|
|
|
snd_pcm_period_elapsed(substream);
|
|
}
|
|
|
|
static int imx_rpmsg_prepare_and_submit(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct rpmsg_msg *msg;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_BUFFER];
|
|
msg->s_msg.header.cmd = TX_BUFFER;
|
|
} else {
|
|
msg = &info->msg[RX_BUFFER];
|
|
msg->s_msg.header.cmd = RX_BUFFER;
|
|
}
|
|
|
|
/* Send buffer address and buffer size */
|
|
msg->s_msg.param.buffer_addr = substream->runtime->dma_addr;
|
|
msg->s_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream);
|
|
msg->s_msg.param.period_size = snd_pcm_lib_period_bytes(substream);
|
|
msg->s_msg.param.buffer_tail = 0;
|
|
|
|
info->num_period[substream->stream] = msg->s_msg.param.buffer_size /
|
|
msg->s_msg.param.period_size;
|
|
|
|
info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete;
|
|
info->callback_param[substream->stream] = substream;
|
|
|
|
return imx_rpmsg_insert_workqueue(substream, msg, info);
|
|
}
|
|
|
|
static int imx_rpmsg_async_issue_pending(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct rpmsg_msg *msg;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_START];
|
|
msg->s_msg.header.cmd = TX_START;
|
|
} else {
|
|
msg = &info->msg[RX_START];
|
|
msg->s_msg.header.cmd = RX_START;
|
|
}
|
|
|
|
return imx_rpmsg_insert_workqueue(substream, msg, info);
|
|
}
|
|
|
|
static int imx_rpmsg_restart(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct rpmsg_msg *msg;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_RESTART];
|
|
msg->s_msg.header.cmd = TX_RESTART;
|
|
} else {
|
|
msg = &info->msg[RX_RESTART];
|
|
msg->s_msg.header.cmd = RX_RESTART;
|
|
}
|
|
|
|
return imx_rpmsg_insert_workqueue(substream, msg, info);
|
|
}
|
|
|
|
static int imx_rpmsg_pause(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct rpmsg_msg *msg;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_PAUSE];
|
|
msg->s_msg.header.cmd = TX_PAUSE;
|
|
} else {
|
|
msg = &info->msg[RX_PAUSE];
|
|
msg->s_msg.header.cmd = RX_PAUSE;
|
|
}
|
|
|
|
return imx_rpmsg_insert_workqueue(substream, msg, info);
|
|
}
|
|
|
|
static int imx_rpmsg_terminate_all(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
struct rpmsg_msg *msg;
|
|
int cmd;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_TERMINATE];
|
|
msg->s_msg.header.cmd = TX_TERMINATE;
|
|
/* Clear buffer count*/
|
|
cmd = TX_PERIOD_DONE + MSG_TYPE_A_NUM;
|
|
info->msg[cmd].s_msg.param.buffer_tail = 0;
|
|
info->msg[cmd].r_msg.param.buffer_tail = 0;
|
|
info->msg[TX_POINTER].r_msg.param.buffer_offset = 0;
|
|
} else {
|
|
msg = &info->msg[RX_TERMINATE];
|
|
msg->s_msg.header.cmd = RX_TERMINATE;
|
|
/* Clear buffer count*/
|
|
cmd = RX_PERIOD_DONE + MSG_TYPE_A_NUM;
|
|
info->msg[cmd].s_msg.param.buffer_tail = 0;
|
|
info->msg[cmd].r_msg.param.buffer_tail = 0;
|
|
info->msg[RX_POINTER].r_msg.param.buffer_offset = 0;
|
|
}
|
|
|
|
del_timer(&info->stream_timer[substream->stream].timer);
|
|
|
|
return imx_rpmsg_insert_workqueue(substream, msg, info);
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_trigger(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream, int cmd)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
|
|
int ret = 0;
|
|
|
|
switch (cmd) {
|
|
case SNDRV_PCM_TRIGGER_START:
|
|
ret = imx_rpmsg_prepare_and_submit(component, substream);
|
|
if (ret)
|
|
return ret;
|
|
ret = imx_rpmsg_async_issue_pending(component, substream);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_RESUME:
|
|
if (rpmsg->force_lpa)
|
|
break;
|
|
fallthrough;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
|
|
ret = imx_rpmsg_restart(component, substream);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_SUSPEND:
|
|
if (!rpmsg->force_lpa) {
|
|
if (runtime->info & SNDRV_PCM_INFO_PAUSE)
|
|
ret = imx_rpmsg_pause(component, substream);
|
|
else
|
|
ret = imx_rpmsg_terminate_all(component, substream);
|
|
}
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
|
|
ret = imx_rpmsg_pause(component, substream);
|
|
break;
|
|
case SNDRV_PCM_TRIGGER_STOP:
|
|
ret = imx_rpmsg_terminate_all(component, substream);
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* imx_rpmsg_pcm_ack
|
|
*
|
|
* Send the period index to M core through rpmsg, but not send
|
|
* all the period index to M core, reduce some unnessesary msg
|
|
* to reduce the pressure of rpmsg bandwidth.
|
|
*/
|
|
static int imx_rpmsg_pcm_ack(struct snd_soc_component *component,
|
|
struct snd_pcm_substream *substream)
|
|
{
|
|
struct snd_pcm_runtime *runtime = substream->runtime;
|
|
struct snd_soc_pcm_runtime *rtd = substream->private_data;
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
|
|
struct rpmsg_info *info = dev_get_drvdata(component->dev);
|
|
snd_pcm_uframes_t period_size = runtime->period_size;
|
|
snd_pcm_sframes_t avail;
|
|
struct timer_list *timer;
|
|
struct rpmsg_msg *msg;
|
|
unsigned long flags;
|
|
int buffer_tail = 0;
|
|
int written_num;
|
|
|
|
if (!rpmsg->force_lpa)
|
|
return 0;
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
|
|
msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM];
|
|
msg->s_msg.header.cmd = TX_PERIOD_DONE;
|
|
} else {
|
|
msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM];
|
|
msg->s_msg.header.cmd = RX_PERIOD_DONE;
|
|
}
|
|
|
|
msg->s_msg.header.type = MSG_TYPE_C;
|
|
|
|
buffer_tail = (frames_to_bytes(runtime, runtime->control->appl_ptr) %
|
|
snd_pcm_lib_buffer_bytes(substream));
|
|
buffer_tail = buffer_tail / snd_pcm_lib_period_bytes(substream);
|
|
|
|
/* There is update for period index */
|
|
if (buffer_tail != msg->s_msg.param.buffer_tail) {
|
|
written_num = buffer_tail - msg->s_msg.param.buffer_tail;
|
|
if (written_num < 0)
|
|
written_num += runtime->periods;
|
|
|
|
msg->s_msg.param.buffer_tail = buffer_tail;
|
|
|
|
/* The notification message is updated to latest */
|
|
spin_lock_irqsave(&info->lock[substream->stream], flags);
|
|
memcpy(&info->notify[substream->stream], msg,
|
|
sizeof(struct rpmsg_s_msg));
|
|
info->notify_updated[substream->stream] = true;
|
|
spin_unlock_irqrestore(&info->lock[substream->stream], flags);
|
|
|
|
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
|
|
avail = snd_pcm_playback_hw_avail(runtime);
|
|
else
|
|
avail = snd_pcm_capture_hw_avail(runtime);
|
|
|
|
timer = &info->stream_timer[substream->stream].timer;
|
|
/*
|
|
* If the data in the buffer is less than one period before
|
|
* this fill, which means the data may not enough on M
|
|
* core side, we need to send message immediately to let
|
|
* M core know the pointer is updated.
|
|
* if there is more than one period data in the buffer before
|
|
* this fill, which means the data is enough on M core side,
|
|
* we can delay one period (using timer) to send the message
|
|
* for reduce the message number in workqueue, because the
|
|
* pointer may be updated by ack function later, we can
|
|
* send latest pointer to M core side.
|
|
*/
|
|
if ((avail - written_num * period_size) <= period_size) {
|
|
imx_rpmsg_insert_workqueue(substream, msg, info);
|
|
} else if (rpmsg->force_lpa && !timer_pending(timer)) {
|
|
int time_msec;
|
|
|
|
time_msec = (int)(runtime->period_size * 1000 / runtime->rate);
|
|
mod_timer(timer, jiffies + msecs_to_jiffies(time_msec));
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_new(struct snd_soc_component *component,
|
|
struct snd_soc_pcm_runtime *rtd)
|
|
{
|
|
struct snd_card *card = rtd->card->snd_card;
|
|
struct snd_pcm *pcm = rtd->pcm;
|
|
struct snd_soc_dai *cpu_dai = asoc_rtd_to_cpu(rtd, 0);
|
|
struct fsl_rpmsg *rpmsg = dev_get_drvdata(cpu_dai->dev);
|
|
int ret;
|
|
|
|
ret = dma_coerce_mask_and_coherent(card->dev, DMA_BIT_MASK(32));
|
|
if (ret)
|
|
return ret;
|
|
|
|
return snd_pcm_set_fixed_buffer_all(pcm, SNDRV_DMA_TYPE_DEV_WC,
|
|
pcm->card->dev, rpmsg->buffer_size);
|
|
}
|
|
|
|
static const struct snd_soc_component_driver imx_rpmsg_soc_component = {
|
|
.name = IMX_PCM_DRV_NAME,
|
|
.pcm_construct = imx_rpmsg_pcm_new,
|
|
.open = imx_rpmsg_pcm_open,
|
|
.close = imx_rpmsg_pcm_close,
|
|
.hw_params = imx_rpmsg_pcm_hw_params,
|
|
.trigger = imx_rpmsg_pcm_trigger,
|
|
.pointer = imx_rpmsg_pcm_pointer,
|
|
.ack = imx_rpmsg_pcm_ack,
|
|
.prepare = imx_rpmsg_pcm_prepare,
|
|
};
|
|
|
|
static void imx_rpmsg_pcm_work(struct work_struct *work)
|
|
{
|
|
struct work_of_rpmsg *work_of_rpmsg;
|
|
bool is_notification = false;
|
|
struct rpmsg_info *info;
|
|
struct rpmsg_msg msg;
|
|
unsigned long flags;
|
|
|
|
work_of_rpmsg = container_of(work, struct work_of_rpmsg, work);
|
|
info = work_of_rpmsg->info;
|
|
|
|
/*
|
|
* Every work in the work queue, first we check if there
|
|
* is update for period is filled, because there may be not
|
|
* enough data in M core side, need to let M core know
|
|
* data is updated immediately.
|
|
*/
|
|
spin_lock_irqsave(&info->lock[TX], flags);
|
|
if (info->notify_updated[TX]) {
|
|
memcpy(&msg, &info->notify[TX], sizeof(struct rpmsg_s_msg));
|
|
info->notify_updated[TX] = false;
|
|
spin_unlock_irqrestore(&info->lock[TX], flags);
|
|
info->send_message(&msg, info);
|
|
} else {
|
|
spin_unlock_irqrestore(&info->lock[TX], flags);
|
|
}
|
|
|
|
spin_lock_irqsave(&info->lock[RX], flags);
|
|
if (info->notify_updated[RX]) {
|
|
memcpy(&msg, &info->notify[RX], sizeof(struct rpmsg_s_msg));
|
|
info->notify_updated[RX] = false;
|
|
spin_unlock_irqrestore(&info->lock[RX], flags);
|
|
info->send_message(&msg, info);
|
|
} else {
|
|
spin_unlock_irqrestore(&info->lock[RX], flags);
|
|
}
|
|
|
|
/* Skip the notification message for it has been processed above */
|
|
if (work_of_rpmsg->msg.s_msg.header.type == MSG_TYPE_C &&
|
|
(work_of_rpmsg->msg.s_msg.header.cmd == TX_PERIOD_DONE ||
|
|
work_of_rpmsg->msg.s_msg.header.cmd == RX_PERIOD_DONE))
|
|
is_notification = true;
|
|
|
|
if (!is_notification)
|
|
info->send_message(&work_of_rpmsg->msg, info);
|
|
|
|
/* update read index */
|
|
spin_lock_irqsave(&info->wq_lock, flags);
|
|
info->work_read_index++;
|
|
info->work_read_index %= WORK_MAX_NUM;
|
|
spin_unlock_irqrestore(&info->wq_lock, flags);
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_probe(struct platform_device *pdev)
|
|
{
|
|
struct snd_soc_component *component;
|
|
struct rpmsg_info *info;
|
|
int ret, i;
|
|
|
|
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
|
|
if (!info)
|
|
return -ENOMEM;
|
|
|
|
platform_set_drvdata(pdev, info);
|
|
|
|
info->rpdev = container_of(pdev->dev.parent, struct rpmsg_device, dev);
|
|
info->dev = &pdev->dev;
|
|
/* Setup work queue */
|
|
info->rpmsg_wq = alloc_ordered_workqueue(info->rpdev->id.name,
|
|
WQ_HIGHPRI |
|
|
WQ_UNBOUND |
|
|
WQ_FREEZABLE);
|
|
if (!info->rpmsg_wq) {
|
|
dev_err(&pdev->dev, "workqueue create failed\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Write index initialize 1, make it differ with the read index */
|
|
info->work_write_index = 1;
|
|
info->send_message = imx_rpmsg_pcm_send_message;
|
|
|
|
for (i = 0; i < WORK_MAX_NUM; i++) {
|
|
INIT_WORK(&info->work_list[i].work, imx_rpmsg_pcm_work);
|
|
info->work_list[i].info = info;
|
|
}
|
|
|
|
/* Initialize msg */
|
|
for (i = 0; i < MSG_MAX_NUM; i++) {
|
|
info->msg[i].s_msg.header.cate = IMX_RPMSG_AUDIO;
|
|
info->msg[i].s_msg.header.major = IMX_RMPSG_MAJOR;
|
|
info->msg[i].s_msg.header.minor = IMX_RMPSG_MINOR;
|
|
info->msg[i].s_msg.header.type = MSG_TYPE_A;
|
|
info->msg[i].s_msg.param.audioindex = 0;
|
|
}
|
|
|
|
init_completion(&info->cmd_complete);
|
|
mutex_init(&info->msg_lock);
|
|
spin_lock_init(&info->lock[TX]);
|
|
spin_lock_init(&info->lock[RX]);
|
|
spin_lock_init(&info->wq_lock);
|
|
|
|
ret = devm_snd_soc_register_component(&pdev->dev,
|
|
&imx_rpmsg_soc_component,
|
|
NULL, 0);
|
|
if (ret)
|
|
goto fail;
|
|
|
|
component = snd_soc_lookup_component(&pdev->dev, NULL);
|
|
if (!component) {
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
/* platform component name is used by machine driver to link with */
|
|
component->name = info->rpdev->id.name;
|
|
|
|
#ifdef CONFIG_DEBUG_FS
|
|
component->debugfs_prefix = "rpmsg";
|
|
#endif
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
if (info->rpmsg_wq)
|
|
destroy_workqueue(info->rpmsg_wq);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void imx_rpmsg_pcm_remove(struct platform_device *pdev)
|
|
{
|
|
struct rpmsg_info *info = platform_get_drvdata(pdev);
|
|
|
|
if (info->rpmsg_wq)
|
|
destroy_workqueue(info->rpmsg_wq);
|
|
}
|
|
|
|
#ifdef CONFIG_PM
|
|
static int imx_rpmsg_pcm_runtime_resume(struct device *dev)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(dev);
|
|
|
|
cpu_latency_qos_add_request(&info->pm_qos_req, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_runtime_suspend(struct device *dev)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(dev);
|
|
|
|
cpu_latency_qos_remove_request(&info->pm_qos_req);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int imx_rpmsg_pcm_suspend(struct device *dev)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(dev);
|
|
struct rpmsg_msg *rpmsg_tx;
|
|
struct rpmsg_msg *rpmsg_rx;
|
|
|
|
rpmsg_tx = &info->msg[TX_SUSPEND];
|
|
rpmsg_rx = &info->msg[RX_SUSPEND];
|
|
|
|
rpmsg_tx->s_msg.header.cmd = TX_SUSPEND;
|
|
info->send_message(rpmsg_tx, info);
|
|
|
|
rpmsg_rx->s_msg.header.cmd = RX_SUSPEND;
|
|
info->send_message(rpmsg_rx, info);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int imx_rpmsg_pcm_resume(struct device *dev)
|
|
{
|
|
struct rpmsg_info *info = dev_get_drvdata(dev);
|
|
struct rpmsg_msg *rpmsg_tx;
|
|
struct rpmsg_msg *rpmsg_rx;
|
|
|
|
rpmsg_tx = &info->msg[TX_RESUME];
|
|
rpmsg_rx = &info->msg[RX_RESUME];
|
|
|
|
rpmsg_tx->s_msg.header.cmd = TX_RESUME;
|
|
info->send_message(rpmsg_tx, info);
|
|
|
|
rpmsg_rx->s_msg.header.cmd = RX_RESUME;
|
|
info->send_message(rpmsg_rx, info);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* CONFIG_PM_SLEEP */
|
|
|
|
static const struct dev_pm_ops imx_rpmsg_pcm_pm_ops = {
|
|
SET_RUNTIME_PM_OPS(imx_rpmsg_pcm_runtime_suspend,
|
|
imx_rpmsg_pcm_runtime_resume,
|
|
NULL)
|
|
SET_SYSTEM_SLEEP_PM_OPS(imx_rpmsg_pcm_suspend,
|
|
imx_rpmsg_pcm_resume)
|
|
};
|
|
|
|
static struct platform_driver imx_pcm_rpmsg_driver = {
|
|
.probe = imx_rpmsg_pcm_probe,
|
|
.remove_new = imx_rpmsg_pcm_remove,
|
|
.driver = {
|
|
.name = IMX_PCM_DRV_NAME,
|
|
.pm = &imx_rpmsg_pcm_pm_ops,
|
|
},
|
|
};
|
|
module_platform_driver(imx_pcm_rpmsg_driver);
|
|
|
|
MODULE_DESCRIPTION("Freescale SoC Audio RPMSG PCM interface");
|
|
MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>");
|
|
MODULE_ALIAS("platform:" IMX_PCM_DRV_NAME);
|
|
MODULE_LICENSE("GPL v2");
|