rpmsg audio

本文深入探讨了RPMsg(Remote Procedure Message)在音频处理中的应用,讲解了如何利用RPMsg进行高效的跨核心通信,实现音频数据的实时传输和处理。通过实例分析,阐述了RPMsg在嵌入式系统尤其是音频设备中的优势和具体实现步骤。
摘要由CSDN通过智能技术生成
/*
 * Copyright (C) 2017 NXP
 *
 * The code contained herein is licensed under the GNU General Public
 * License. You may obtain a copy of the GNU General Public License
 * Version 2 or later at the following locations:
 *
 * http://www.opensource.org/licenses/gpl-license.html
 * http://www.gnu.org/copyleft/gpl.html
 */

#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/i2c.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/clk.h>
#include <sound/soc.h>
#include <sound/jack.h>
#include <sound/control.h>
#include <sound/pcm_params.h>
#include <sound/soc-dapm.h>
#include <linux/pinctrl/consumer.h>
#include "fsl_rpmsg_i2s.h"

struct imx_rpmsg_data {
	struct snd_soc_dai_link dai[1];
	struct snd_soc_card card;
};

static const struct snd_soc_dapm_widget imx_wm8960_dapm_widgets[] = {
	SND_SOC_DAPM_HP("Headphone Jack", NULL),
	SND_SOC_DAPM_SPK("Ext Spk", NULL),
	SND_SOC_DAPM_MIC("Mic Jack", NULL),
	SND_SOC_DAPM_MIC("Main MIC", NULL),
};

static int imx_rpmsg_probe(struct platform_device *pdev)
{
	struct device_node *cpu_np;
	struct platform_device *cpu_pdev;
	struct imx_rpmsg_data *data;
	struct fsl_rpmsg_i2s         *rpmsg_i2s;
	int ret;

	cpu_np = of_parse_phandle(pdev->dev.of_node, "cpu-dai", 0);
	if (!cpu_np) {
		dev_err(&pdev->dev, "cpu dai phandle missing or invalid\n");
		ret = -EINVAL;
		goto fail;
	}

	data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
	if (!data) {
		ret = -ENOMEM;
		goto fail;
	}

	cpu_pdev = of_find_device_by_node(cpu_np);
	if (!cpu_pdev) {
		dev_err(&pdev->dev, "failed to find rpmsg platform device\n");
		ret = -EINVAL;
		goto fail;
	}

	rpmsg_i2s = platform_get_drvdata(cpu_pdev);

	data->dai[0].name = "rpmsg hifi";
	data->dai[0].stream_name = "rpmsg hifi";
	data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
			    SND_SOC_DAIFMT_NB_NF |
			    SND_SOC_DAIFMT_CBM_CFM;

	if (rpmsg_i2s->codec_wm8960) {
		data->dai[0].codec_dai_name = "rpmsg-wm8960-hifi";
		data->dai[0].codec_name = "rpmsg-audio-codec-wm8960";
	}

	if (rpmsg_i2s->codec_dummy) {
		data->dai[0].codec_dai_name = "snd-soc-dummy-dai";
		data->dai[0].codec_name = "snd-soc-dummy";
	}

	if (rpmsg_i2s->codec_ak4497) {
		data->dai[0].codec_dai_name = "rpmsg-ak4497-aif";
		data->dai[0].codec_name = "rpmsg-audio-codec-ak4497";
		data->dai[0].dai_fmt = SND_SOC_DAIFMT_I2S |
			    SND_SOC_DAIFMT_NB_NF |
			    SND_SOC_DAIFMT_CBS_CFS;
	}

	data->dai[0].cpu_dai_name = dev_name(&cpu_pdev->dev);
	data->dai[0].platform_of_node = cpu_np;
	data->dai[0].playback_only = true;
	data->dai[0].capture_only = true;
	data->card.num_links = 1;
	data->card.dai_link = data->dai;

	if (of_property_read_bool(pdev->dev.of_node, "rpmsg-out"))
		data->dai[0].capture_only = false;

	if (of_property_read_bool(pdev->dev.of_node, "rpmsg-in"))
		data->dai[0].playback_only = false;

	if (data->dai[0].playback_only && data->dai[0].capture_only) {
		dev_err(&pdev->dev, "no enabled rpmsg DAI link\n");
		ret = -EINVAL;
		goto fail;
	}

	data->card.dev = &pdev->dev;
	data->card.owner = THIS_MODULE;
	ret = snd_soc_of_parse_card_name(&data->card, "model");
	if (ret)
		goto fail;

	if (rpmsg_i2s->codec_wm8960) {
		ret = snd_soc_of_parse_audio_routing(&data->card,
						"audio-routing");
		if (ret)
			goto fail;

		data->card.dapm_widgets = imx_wm8960_dapm_widgets;
		data->card.num_dapm_widgets =
				ARRAY_SIZE(imx_wm8960_dapm_widgets);
	}

	platform_set_drvdata(pdev, &data->card);
	snd_soc_card_set_drvdata(&data->card, data);
	ret = devm_snd_soc_register_card(&pdev->dev, &data->card);
	if (ret) {
		dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n", ret);
		goto fail;
	}

fail:
	if (cpu_np)
		of_node_put(cpu_np);
	return ret;
}

static const struct of_device_id imx_rpmsg_dt_ids[] = {
	{ .compatible = "fsl,imx-audio-rpmsg", },
	{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_rpmsg_dt_ids);

static struct platform_driver imx_rpmsg_driver = {
	.driver = {
		.name = "imx-audio-rpmsg",
		.owner = THIS_MODULE,
		.pm = &snd_soc_pm_ops,
		.of_match_table = imx_rpmsg_dt_ids,
	},
	.probe = imx_rpmsg_probe,
};
module_platform_driver(imx_rpmsg_driver);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("Freescale i.MX rpmsg audio ASoC machine driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:imx-rpmsg");

 

 

/*
 * imx-rpmsg-platform.c  --  ALSA Soc Audio Layer
 *
 * Copyright 2017 NXP
 *
 * This program is free software; you can redistribute  it and/or modify it
 * under  the terms of  the GNU General  Public License as published by the
 * Free Software Foundation;  either version 2 of the  License, or (at your
 * option) any later version.
 */
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/imx_rpmsg.h>
#include <linux/delay.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_i2s.h"
#include "../../core/pcm_local.h"

struct i2s_info *i2s_info_g;

static struct snd_pcm_hardware imx_rpmsg_pcm_hardware = {
	.info = SNDRV_PCM_INFO_INTERLEAVED |
		SNDRV_PCM_INFO_BLOCK_TRANSFER |
		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_SAI_DMABUF_SIZE,
	.period_bytes_min = 512,
	.period_bytes_max = 65532, /* Limited by SDMA engine */
	.periods_min = 2,
	.periods_max = 6000,
	.fifo_size = 0,
};

static int imx_rpmsg_pcm_hw_params(struct snd_pcm_substream *substream,
			      struct snd_pcm_hw_params *params)
{

	struct snd_pcm_runtime *runtime = substream->runtime;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
	struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
	struct i2s_info  *i2s_info =  &rpmsg_i2s->i2s_info;
	struct i2s_rpmsg *rpmsg;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg = &i2s_info->rpmsg[I2S_TX_HW_PARAM];
	else
		rpmsg = &i2s_info->rpmsg[I2S_RX_HW_PARAM];

	rpmsg->send_msg.param.rate = params_rate(params);

	if (params_format(params) == SNDRV_PCM_FORMAT_S16_LE)
		rpmsg->send_msg.param.format   = RPMSG_S16_LE;
	else if (params_format(params) == SNDRV_PCM_FORMAT_S24_LE)
		rpmsg->send_msg.param.format   = RPMSG_S24_LE;
	else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U16_LE)
		rpmsg->send_msg.param.format   = SNDRV_PCM_FORMAT_DSD_U16_LE;
	else if (params_format(params) == SNDRV_PCM_FORMAT_DSD_U32_LE)
		rpmsg->send_msg.param.format   = SNDRV_PCM_FORMAT_DSD_U32_LE;
	else
		rpmsg->send_msg.param.format   = RPMSG_S32_LE;

	if (params_channels(params) == 1)
		rpmsg->send_msg.param.channels = RPMSG_CH_LEFT;
	else
		rpmsg->send_msg.param.channels = RPMSG_CH_STEREO;

	snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
	runtime->dma_bytes = params_buffer_bytes(params);

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg->send_msg.header.cmd = I2S_TX_HW_PARAM;
	else
		rpmsg->send_msg.header.cmd = I2S_RX_HW_PARAM;

	i2s_info->send_message(rpmsg, i2s_info);

	return 0;
}

static int imx_rpmsg_pcm_hw_free(struct snd_pcm_substream *substream)
{
	snd_pcm_set_runtime_buffer(substream, NULL);
	return 0;
}

static snd_pcm_uframes_t imx_rpmsg_pcm_pointer(
				struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai   *cpu_dai = rtd->cpu_dai;
	struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
	struct i2s_info      *i2s_info =  &rpmsg_i2s->i2s_info;
	unsigned int pos = 0;
	struct i2s_rpmsg *rpmsg;
	int buffer_tail = 0;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
	else
		rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];

	buffer_tail = rpmsg->recv_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(unsigned long data)
{
	struct snd_pcm_substream *substream = (struct snd_pcm_substream *)data;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai   *cpu_dai = rtd->cpu_dai;
	struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
	struct i2s_info      *i2s_info =  &rpmsg_i2s->i2s_info;
	struct i2s_rpmsg     *rpmsg;
	unsigned long flags;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg = &i2s_info->rpmsg[I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM];
	else
		rpmsg = &i2s_info->rpmsg[I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM];

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg->send_msg.header.cmd = I2S_TX_PERIOD_DONE;
	else
		rpmsg->send_msg.header.cmd = I2S_RX_PERIOD_DONE;

	spin_lock_irqsave(&i2s_info->wq_lock, flags);
	if (i2s_info->work_write_index != i2s_info->work_read_index) {
		int index = i2s_info->work_write_index;
		memcpy(&i2s_info->work_list[index].msg, rpmsg,
					sizeof(struct i2s_rpmsg_s));
		queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
		i2s_info->work_write_index++;
		i2s_info->work_write_index %= WORK_MAX_NUM;
	} else
		i2s_info->msg_drop_count[substream->stream]++;
	spin_unlock_irqrestore(&i2s_info->wq_lock, flags);
}

static int imx_rpmsg_pcm_open(struct snd_pcm_substream *substream)
{
	int ret = 0;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai   *cpu_dai = rtd->cpu_dai;
	struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
	struct i2s_info      *i2s_info =  &rpmsg_i2s->i2s_info;
	struct i2s_rpmsg     *rpmsg;
	struct dmaengine_pcm_runtime_data *prtd;
	int cmd;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg = &i2s_info->rpmsg[I2S_TX_OPEN];
	else
		rpmsg = &i2s_info->rpmsg[I2S_RX_OPEN];

	imx_rpmsg_pcm_hardware.buffer_bytes_max =
					i2s_info->prealloc_buffer_size;
	imx_rpmsg_pcm_hardware.period_bytes_max =
			imx_rpmsg_pcm_hardware.buffer_bytes_max / 2;

	snd_soc_set_runtime_hwparams(substream, &imx_rpmsg_pcm_hardware);

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg->send_msg.header.cmd = I2S_TX_OPEN;
	else
		rpmsg->send_msg.header.cmd = I2S_RX_OPEN;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
		cmd = I2S_TX_PERIOD_DONE + I2S_TYPE_A_NUM;
		i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
		i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
		i2s_info->rpmsg[I2S_TX_POINTER].recv_msg.param.buffer_offset = 0;
	} else {
		cmd = I2S_RX_PERIOD_DONE + I2S_TYPE_A_NUM;
		i2s_info->rpmsg[cmd].send_msg.param.buffer_tail = 0;
		i2s_info->rpmsg[cmd].recv_msg.param.buffer_tail = 0;
		i2s_info->rpmsg[I2S_RX_POINTER].recv_msg.param.buffer_offset = 0;
	}

	i2s_info->send_message(rpmsg, i2s_info);

	prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
	if (!prtd)
		return -ENOMEM;

	substream->runtime->private_data = prtd;

	ret = snd_pcm_hw_constraint_integer(substream->runtime,
					    SNDRV_PCM_HW_PARAM_PERIODS);
	if (ret < 0)
		return ret;

	i2s_info->msg_drop_count[substream->stream] = 0;

	/*create thread*/
	setup_timer(&i2s_info->stream_timer[substream->stream],
			imx_rpmsg_timer_callback, (unsigned long)substream);

	return ret;
}

static int imx_rpmsg_pcm_close(struct snd_pcm_substream *substream)
{
	int ret = 0;
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai   *cpu_dai = rtd->cpu_dai;
	struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
	struct i2s_info      *i2s_info =  &rpmsg_i2s->i2s_info;
	struct i2s_rpmsg     *rpmsg;
	struct dmaengine_pcm_runtime_data *prtd =
					substream->runtime->private_data;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg = &i2s_info->rpmsg[I2S_TX_CLOSE];
	else
		rpmsg = &i2s_info->rpmsg[I2S_RX_CLOSE];

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg->send_msg.header.cmd = I2S_TX_CLOSE;
	else
		rpmsg->send_msg.header.cmd = I2S_RX_CLOSE;
	flush_workqueue(i2s_info->rpmsg_wq);
	i2s_info->send_message(rpmsg, i2s_info);

	del_timer(&i2s_info->stream_timer[substream->stream]);

	kfree(prtd);

	rtd->dai_link->ignore_suspend = 0;

	if (i2s_info->msg_drop_count[substream->stream])
		dev_warn(rtd->dev, "Msg is dropped!, number is %d\n",
			i2s_info->msg_drop_count[substream->stream]);

	return ret;
}

static int imx_rpmsg_pcm_prepare(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 = rtd->cpu_dai;
	struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);

	/* NON-MMAP mode, NONBLOCK, Version 2, enable lpa in dts
	 * four condition to determine the lpa is enabled.
	 */
	if ((runtime->access == SNDRV_PCM_ACCESS_RW_INTERLEAVED ||
		runtime->access == SNDRV_PCM_ACCESS_RW_NONINTERLEAVED) &&
			rpmsg_i2s->version == 2 &&
			rpmsg_i2s->enable_lpa) {
		rtd->dai_link->ignore_suspend = 1;
		rpmsg_i2s->force_lpa = 1;
	} else
		rpmsg_i2s->force_lpa = 0;

	return 0;
}

static int imx_rpmsg_pcm_mmap(struct snd_pcm_substream *substream,
	struct vm_area_struct *vma)
{
	struct snd_pcm_runtime *runtime = substream->runtime;

	return dma_mmap_writecombine(substream->pcm->card->dev, vma,
				     runtime->dma_area,
				     runtime->dma_addr,
				     runtime->dma_bytes);
}

static void imx_rpmsg_pcm_dma_complete(void *arg)
{
	struct snd_pcm_substream *substream = arg;

	snd_pcm_period_elapsed(substream);
}

static int imx_rpmsg_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
{
	struct snd_soc_pcm_runtime *rtd = substream->private_data;
	struct snd_soc_dai   *cpu_dai = rtd->cpu_dai;
	struct fsl_rpmsg_i2s *rpmsg_i2s = dev_get_drvdata(cpu_dai->dev);
	struct i2s_info      *i2s_info =  &rpmsg_i2s->i2s_info;
	struct i2s_rpmsg   *rpmsg;
	unsigned long flags;

	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg = &i2s_info->rpmsg[I2S_TX_BUFFER];
	else
		rpmsg = &i2s_info->rpmsg[I2S_RX_BUFFER];


	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
		rpmsg->send_msg.header.cmd = I2S_TX_BUFFER;
	else
		rpmsg->send_msg.header.cmd = I2S_RX_BUFFER;

	rpmsg->send_msg.param.buffer_addr = substream->runtime->dma_addr;
	rpmsg->send_msg.param.buffer_size = snd_pcm_lib_buffer_bytes(substream);
	rpmsg->send_msg.param.period_size = snd_pcm_lib_period_bytes(substream);
	rpmsg->send_msg.param.buffer_tail = 0;

	i2s_info->num_period[substream->stream] =
			rpmsg->send_msg.param.buffer_size /
				rpmsg->send_msg.param.period_size;

	i2s_info->callback[substream->stream] = imx_rpmsg_pcm_dma_complete;
	i2s_info->callback_param[substream->stream] = substream;

	spin_lock_irqsave(&i2s_info->wq_lock, flags);
	if (i2s_info->work_write_index != i2s_info->work_read_index) {
		int index = i2s_info->work_write_index;
		memcpy(&i2s_info->work_list[index].msg, rpmsg,
					sizeof(struct i2s_rpmsg_s));
		queue_work(i2s_info->rpmsg_wq, &i2s_info->work_list[index].work);
		i2s_info->work_w
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值