前一章节:
三、参考资料
3.1 文章
https://blog.csdn.net/pingis58/article/details/109235204
3.2 手册
max98357a用i2s接口,我们只要用如下引脚:
LRC:左右声道选择
BCLK:采样信号
DIN:音频数据输入
Vin:电源
GND:接地
gain和sd-mode驱动里也有定义,需要自己增加接口去控制,后续再说。
四、驱动路径
max98357a驱动:
duo-buildroot-sdk-develop\linux_5.10\sound\soc\codecs\max98357a.c
duo-buildroot-sdk-develop\linux_5.10\sound\soc\codecs\Makefile
snd-soc-max98357a-objs := max98357a.o
obj-$(CONFIG_SND_SOC_MAX98357A) += snd-soc-max98357a.o
duo-buildroot-sdk-develop\linux_5.10\sound\soc\Makefile
obj-$(CONFIG_SND_SOC) += snd-soc-core.o
obj-$(CONFIG_SND_SOC) += codecs/
duo-buildroot-sdk-develop\linux_5.10\sound\Makefile
obj-$(CONFIG_SOUND) += soundcore.o
obj-$(CONFIG_SND) += core/ i2c/ drivers/ isa/ pci/ ppc/ arm/ sh/ synth/ usb/ \
firewire/ sparc/ spi/ parisc/ pcmcia/ mips/ soc/ atmel/ hda/ x86/ xen/
duo-buildroot-sdk-develop\linux_5.10\Makefile
drivers-y := drivers/ sound/
所以需要打开的config如下
CONFIG_SND_SOC=y
CONFIG_SOUND=y
CONFIG_SND=y
五、添加codec驱动——max98357a
5.1 config
如上章介绍的,codec路径下的配置,所以加载codec驱动需要配置config。
CONFIG_SND_SOC_MAX98357A=y
5.2 dtsi
duo-buildroot-sdk-develop\linux_5.10\sound\soc\codecs\max98357a.c
#ifdef CONFIG_OF
static const struct of_device_id max98357a_device_id[] = {
{ .compatible = "maxim,max98357a" },
{ .compatible = "maxim,max98360a" },
{}
};
MODULE_DEVICE_TABLE(of, max98357a_device_id);
#endif
dtsi中需要匹配该节点,增加如下配置,让codec可以正常加载。
/* codec */
max98357a: max98357a {
#sound-dai-cells = <0>;
compatible = "maxim,max98357a";
status = "okay";
/*sdmode-gpios = <&gpio1 14 0>;*/
/* max98357a has gain & sd_mode gpio. but codec driver just has sdmode */
};
5.3 dai_driver
这里的dai_driver后续会在machine驱动中使用到,注意name。
static struct snd_soc_dai_driver max98357a_dai_driver = {
.name = "HiFi",
.playback = {
.stream_name = "HiFi Playback",
.formats = SNDRV_PCM_FMTBIT_S16 |
SNDRV_PCM_FMTBIT_S24 |
SNDRV_PCM_FMTBIT_S32,
.rates = SNDRV_PCM_RATE_8000 |
SNDRV_PCM_RATE_16000 |
SNDRV_PCM_RATE_32000 |
SNDRV_PCM_RATE_44100 |
SNDRV_PCM_RATE_48000 |
SNDRV_PCM_RATE_88200 |
SNDRV_PCM_RATE_96000,
.rate_min = 8000,
.rate_max = 96000,
.channels_min = 1,
.channels_max = 2,
},
.ops = &max98357a_dai_ops,
};
六、查看platform/cpu
duo-buildroot-sdk-develop\linux_5.10\sound\soc\cvitek\Makefile
使用的是i2s2,注意需要打开i2s2节点。
Makefile
# Cvitek Platform Support
obj-$(CONFIG_SND_CV1835_I2S) += cv1835_i2s.o
i2s2: i2s@04120000 {
compatible = "cvitek,cv1835-i2s";
reg = <0x0 0x04120000 0x0 0x2000>;
clocks = <&i2s_mclk 0>;
clock-names = "i2sclk";
dev-id = <2>;
#sound-dai-cells = <0>;
dmas = <&dmac 6 1 1 /* read channel */
&dmac 1 1 1>; /* write channel */
dma-names = "rx", "tx";
capability = "txrx";
mclk_out = "false";
};
七、添加machin驱动
7.1 仿照182x,183x
这里定义的cpu_dai_name,由于audio的dapm接口已经变化了,之前的name方式加载被弃用。
若还用下述配置,则导致编译不过。
/* machine -- sound card*/
#ifdef CV1835_EXT_CARD_3_EN
/* sound_ext3 use external codec */
sound_ext3 {
compatible = "cvitek,cv1835-max98357a";
cvi,model = "CV1835";
cvi,mode = "I2S";
cvi,fmt = "IBNF";
cvi,card_name = "cv1835_external_card_max98357a";
cvi,slot_no=<2>;
dai@0 {
cvi,dai_name = "cv1835-i2s-2";
cvi,stream_name = "HiFi";
cvi,cpu_dai_name = "4120000.i2s";
cvi,codec_dai_name = "HiFi";
cvi,platform_name = "4120000.i2s";
cvi,codec_name = "MX98357A:00";
cvi,role = "master";
};
};
#endif
7.2 重新定义dai_link
SND_SOC_DAILINK_DEFS(dailink_max98357a,
DAILINK_COMP_ARRAY(COMP_CPU("4120000.i2s")),
DAILINK_COMP_ARRAY(COMP_CODEC("max98357a", "HiFi")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("4120000.i2s")));
static struct snd_soc_dai_link cv1835_max98357a_dai[] = {
[DPCM_AUDIO_SPKR] = {
.name = "max98357a",
.stream_name = "max98357a i2s", //"cv1835-max98357a",
.init = cv1835_max98357a_asoc_init,
.ops = &cv1835_max98357a_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS,
SND_SOC_DAILINK_REG(dailink_max98357a),
// Interface Abandonment
// .cpu_dai_name= "4120000.i2s",
// .codec_dai_name = "HiFi",
// .codec_name = "maxim,max98357a",
// .platform_name= "4120000.i2s",
},
};
7.3 添加驱动
添加驱动代码
duo-buildroot-sdk-develop\linux_5.10\sound\soc\cvitek\cv1835_max98357a.c
先编译看看能不能成功添加驱动
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Machine driver for EVAL-ADAU1372 on CVITEK CV1835
*
* Copyright 2019 CVITEK
*
* Author: EthanChen
*
*/
#include <linux/module.h>
#include <linux/device.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/soc.h>
#include <sound/pcm_params.h>
#include <linux/io.h>
#include <linux/proc_fs.h>
// // u16 tdm_slot_no = 2;
struct card_private {
int tmp; //save sth.
// struct snd_soc_jack headset;
// struct list_head hdmi_pcm_list;
// struct snd_soc_jack hdmi[3];
};
enum {
DPCM_AUDIO_SPKR = 0,
};
static const struct snd_soc_dapm_widget cv1835_max98357a_dapm_widgets[] = {
SND_SOC_DAPM_SPK("SPKR", NULL), // diff "Speaker" in max98357a
};
static const struct snd_soc_dapm_route cv1835_max98357a_dapm_routes[] = {
{"SPKR", NULL, "Speaker"},
};
static int cv1835_max98357a_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
return 0;
}
static int cv1835_max98357a_asoc_init(struct snd_soc_pcm_runtime *rtd)
{
// dual with private data
// struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
// struct snd_soc_dai *codec_dai = rtd->codec_dai;
// u32 ret;
// /* Only need to set slot number while mode is TDM/PDM, otherwise default slot number is 2 */
// if (tdm_slot_no != 2) {
// ret = snd_soc_dai_set_tdm_slot(codec_dai, 0x0F, 0x0F, tdm_slot_no, 32);
// if (ret < 0)
// return ret;
// ret = snd_soc_dai_set_tdm_slot(cpu_dai, 0x0F, 0x0F, tdm_slot_no, 32);
// if (ret < 0)
// return ret;
// }
return 0;
}
static struct snd_soc_ops cv1835_max98357a_ops = {
.hw_params = cv1835_max98357a_hw_params,
};
SND_SOC_DAILINK_DEFS(dailink_max98357a,
DAILINK_COMP_ARRAY(COMP_CPU("4120000.i2s")),
DAILINK_COMP_ARRAY(COMP_CODEC("max98357a", "HiFi")),
DAILINK_COMP_ARRAY(COMP_PLATFORM("4120000.i2s")));
static struct snd_soc_dai_link cv1835_max98357a_dai[] = {
[DPCM_AUDIO_SPKR] = {
.name = "max98357a",
.stream_name = "max98357a i2s", //"cv1835-max98357a",
.init = cv1835_max98357a_asoc_init,
.ops = &cv1835_max98357a_ops,
.dai_fmt = SND_SOC_DAIFMT_I2S
| SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBS_CFS,
SND_SOC_DAILINK_REG(dailink_max98357a),
},
};
static struct snd_soc_card cv1835_max98357a = {
.name = "cv1835 max98357a", // card name
.owner = THIS_MODULE,
.dai_link = cv1835_max98357a_dai,
.num_links = ARRAY_SIZE(cv1835_max98357a_dai),
// control may don't have
// .controls = skylake_controls,
// .num_controls = ARRAY_SIZE(skylake_controls),
.dapm_widgets = cv1835_max98357a_dapm_widgets,
.num_dapm_widgets = ARRAY_SIZE(cv1835_max98357a_dapm_widgets),
.dapm_routes = cv1835_max98357a_dapm_routes,
.num_dapm_routes = ARRAY_SIZE(cv1835_max98357a_dapm_routes),
// .fully_routed = true,
// .late_probe = skylake_card_late_probe,
};
static const struct of_device_id cvi_audio_match_ids[] = {
{
.compatible = "cvitek,cv1835-max98357a",
//.data = (void *) &cv1835_max98357a_dai,
},
{},
};
MODULE_DEVICE_TABLE(of, cvi_audio_match_ids);
static int cv1835_max98357a_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct snd_soc_card *card = &cv1835_max98357a;
struct card_private *ctx; // create private data for card
int ret;
// struct device_node *np = pdev->dev.of_node, *dai;
// dev_info("snd card name = %s",card->name);
ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
card->dev = &pdev->dev;
snd_soc_card_set_drvdata(card, ctx); //save card info to snd_card
ret = devm_snd_soc_register_card(&pdev->dev, card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed (%d)\n",
ret);
return ret;
}
return 0;
}
static struct platform_driver cv1835_max98357a_driver = {
.driver = {
.name = "cv1835-max98357a",
.pm = &snd_soc_pm_ops,
.of_match_table = cvi_audio_match_ids,
},
.probe = cv1835_max98357a_probe,
};
module_platform_driver(cv1835_max98357a_driver);
MODULE_AUTHOR("EthanChen");
MODULE_DESCRIPTION("ALSA SoC cv1835 max98357a driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:cv1835-max98357a");
7.4 添加Kconfig
config SND_SOC_CV1835_MAX98357A
tristate "Support for the max98357a card"
help
max98357a codec enable.
7.5 添加makefile
obj-$(CONFIG_SND_SOC_CV1835_MAX98357A) += cv1800_max98357a.o
7.6 打开config
CONFIG_SND_SOC_CV1835_MAX98357A为了加载machine驱动。
CONFIG_SND_SOC_MAX98357A加载codec驱动。
CONFIG_SND_CV1835_I2S加载cpu/platform驱动。
CONFIG_SND_PROC_FS打开proc接口实现,用于/proc/asound目录下的声卡信息。
# sound -- max98357a
CONFIG_SND_SOC_CV1835_MAX98357A=y
CONFIG_SND_SOC_MAX98357A=y
CONFIG_SND_PROC_FS=y
CONFIG_SOUND=y
CONFIG_SND=y
CONFIG_SND_SOC=y
CONFIG_SND_CV1835_I2S=y
CONFIG_CV1835_I2S_SUBSYS=y
CONFIG_SND_SOC_CV1835_CONCURRENT_I2S=y
八、问题
8.1 /proc/asound目录没有生成
CONFIG_SND_PROC_FS
查看config最终生成文件
路径:duo-buildroot-sdk\linux_5.10\build\cv1800b_milkv_duo_sd.config
makefile
ifneq ($(CONFIG_SND_PROC_FS),)
snd-y += info.o
snd-$(CONFIG_SND_OSSEMUL) += info_oss.o
endif
asound目录下存放这些内容
info.c
int snd_info_check_reserved_words(const char *str)
{
static const char * const reserved[] =
{
"version",
"meminfo",
"memdebug",
"detect",
"devices",
"oss",
"cards",
"timers",
"synth",
"pcm",
"seq",
NULL
};
const char * const *xstr = reserved;
while (*xstr) {
if (!strcmp(*xstr, str))
return 0;
xstr++;
}
if (!strncmp(str, "card", 4))
return 0;
return 1;
}
alsa源码
duo-buildroot-sdk-develop\linux_5.10\sound\core\sound.c
8.2 使用audio_debug读取adc、i2s0/2,状态未打开
查看dts,状态是ok的
查看dts生成文件
路径:duo-buildroot-sdk\u-boot-2021.10\build\cv1800b_milkv_duo_sd\arch\riscv\dts.cv1800b_milkv_duo_sd.dtb.dts.tmp
由于驱动中没有添加该实现,所以显示的状态不对。
需要在dtsi中添加信息,并在code中增加处理。
8.3 mclk
可能不需要添加,这个看芯片是否支持
duo-buildroot-sdk-develop\linux_5.10\sound\soc\cvitek\cv1835_i2s.c
这里可能需要开config,以及定义dts中mclk_out
device_property_read_string(&pdev->dev, "mclk_out", &mclk_out);
if (!strcmp(mclk_out, "true"))
dev->mclk_out = true;
else
dev->mclk_out = false;
val = i2s_read_reg(dev->i2s_base, I2S_CLK_CTRL0);
val &= ~(AUD_CLK_SOURCE_MASK);
val &= ~(BCLK_OUT_FORCE_EN); /* blck_out output after transmission start */
#if defined(CONFIG_SND_SOC_CV1835_USE_AUDIO_PLL)
if (dev->mclk_out == true)
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, val | AUD_CLK_FROM_PLL | MCLK_OUT_EN | AUD_ENABLE);
/* Turn aud_en on due to external codec might need MCLK to do register initialization */
else
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, val | AUD_CLK_FROM_PLL);
#else
i2s_write_reg(dev->i2s_base, I2S_CLK_CTRL0, val | AUD_CLK_FROM_MCLK_IN);
#endif
九、结果
9.1 编译
CC sound/soc/codecs/max98357a.o
CC sound/soc/cvitek/cv1835_max98357a.o
9.2 设备查看
小结
本章介绍如何添加max98357a的驱动并成功加载。
请结合前一章节一起看。