Using an I2S driver without an audio codec initialization we were able to create a soundcard that can be used with alsa (both aplay/arecord are working, audio is transmitted, the sound is "off" though) but are unable to influence the format of the I2S interface.
Using debug output I could verify that fsl_ssi_set_dai_fmt() is called during driver initialization with the format parameter I configured (SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM) but that doesnt seem to have any effect on the I2S interface of the iMX6.
Even if the format parameter isnt configured at all or I switch to Master Mode the I2S interface doesnt change its operation and is still working the same as always.
Are there any other steps necessary to ensure that a format change applies properly?
已解决! 转到解答。
Hello Weidong,
the code you provided helped me in tracking down the main problem and finally fixing it.
The plattform driver used is still fsl_ssi (imx-ssi doesn't work with a device tree configuration as far as I know) but now it is setup correctly.
What was missing was the call to snd_soc_dai_set_fmt() in imx_hifi_hw_params(). This seems necessary because after the initialziation phase fsl_ssi reverts its fmt parameters every time audio data transfer resumes on the I2S device.
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>
#include <linux/of.h>
#include <../sound/soc/fsl/imx-audmux.h>
struct imx_phone_data {
struct snd_soc_dai_link dai;
struct snd_soc_card card;
struct platform_device *pdev;
};
struct imx_phone_data *data;
static int imx_hifi_startup(struct snd_pcm_substream *substream)
{
return 0;
}
static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct device *dev = data->card.dev;
int ret = 0;
ret = snd_soc_dai_set_fmt(cpu_dai, data->dai.dai_fmt);
if (ret) {
dev_err(dev, "setting format for cpu dai failed\n");
return ret;
}
return 0;
}
static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
{
return 0;
}
static struct snd_soc_ops imx_hifi_ops = {
.startup = imx_hifi_startup,
.hw_params = imx_hifi_hw_params,
.hw_free = imx_hifi_hw_free,
};
static int imx_phone_audio_probe(struct platform_device *pdev)
{
struct device_node *ssi_np, *np = pdev->dev.of_node;
struct platform_device *ssi_pdev;
int ret = 0, int_port, ext_port;
//struct platform_device *cpu_pdev;
/*
Get the int and ext port from the dtb
*/
ret = of_property_read_u32(np, "mux-int-port", &int_port);
if (ret) {
dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
return ret;
}
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
if (ret) {
dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
return ret;
}
/*
* The port numbering in the hardware manual starts at 1, while
* the audmux API expects it starts at 0.
*/
int_port--;
ext_port--;
/*Configure the audmux*/
ret = imx_audmux_v2_configure_port(int_port,
IMX_AUDMUX_V2_PTCR_SYN |
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TFSDIR |
IMX_AUDMUX_V2_PTCR_TCLKDIR,
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
if (ret) {
dev_err(&pdev->dev, "audmux internal port setup failed\n");
return ret;
}
ret = imx_audmux_v2_configure_port(ext_port,
IMX_AUDMUX_V2_PTCR_SYN,
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
if (ret) {
dev_err(&pdev->dev, "audmux external port setup failed\n");
return ret;
}
//Get the i2s controller from dtb
ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
if (!ssi_np) {
dev_err(&pdev->dev, "phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}
//find the i2s node and get its pdev struct
ssi_pdev = of_find_device_by_node(ssi_np);
if (!ssi_pdev) {
dev_err(&pdev->dev, "failed to find SSI platform device\n");
ret = -EINVAL;
goto fail;
}
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
dev_err(&pdev->dev, "failed to allocate memory\n");
ret = -ENOMEM;
goto fail;
}
data->dai.name = "Phone HiFi";
data->dai.stream_name = "Phone Audio";
data->dai.codec_dai_name = "snd-soc-dummy-dai";
data->dai.codec_name = "snd-soc-dummy";
data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev);
data->dai.ops = &imx_hifi_ops;
data->dai.cpu_of_node = ssi_np;
data->dai.platform_of_node = ssi_np;
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF |
SND_SOC_DAIFMT_CBM_CFM;
data->card.dev = &pdev->dev;
data->card.num_links = 1;
data->card.dai_link = &data->dai;
data->pdev = platform_device_register_simple("phone", -1, NULL, 0);
if (IS_ERR(data->pdev)) {
ret = PTR_ERR(data->pdev);
dev_err(&pdev->dev, "register failed: %d\n", ret);
goto fail;
}
//Fill in the card details
ret = snd_soc_of_parse_card_name(&data->card, "model");
if (ret)
goto fail;
data->card.num_links = 1;
data->card.owner = THIS_MODULE;
data->card.dai_link = &data->dai;
platform_set_drvdata(pdev, &data->card);
snd_soc_card_set_drvdata(&data->card, data);
ret = snd_soc_register_card(&data->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret);
goto fail;
}
of_node_put(ssi_np);
return 0;
fail:
if (ssi_np)
of_node_put(ssi_np);
return ret;
}
static int imx_phone_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct imx_phone_data *data = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
platform_device_unregister(data->pdev);
return 0;
}
static const struct of_device_id imx_phone_dt_ids[] = {
{ .compatible = "fsl,imx-audio-phone", },
{ }
};
MODULE_DEVICE_TABLE(of, imx_phone_dt_ids);
static struct platform_driver imx_phone_driver = {
.driver = {
.name = "imx-audio-phone",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = imx_phone_dt_ids,
},
.probe = imx_phone_audio_probe,
.remove = imx_phone_audio_remove,
};
module_platform_driver(imx_phone_driver);
Regards
Christian
We are still using an older kernel (3.10.17) without a SAI implementation. The machine driver is derived from imx-sgtl5000.c just without a full codec implenentation.
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>
#include <linux/of.h>
#include <../sound/soc/fsl/imx-audmux.h>
struct imx_phone_data {
struct snd_soc_dai_link dai;
struct snd_soc_card card;
struct platform_device *pdev;
};
static int imx_phone_audio_probe(struct platform_device *pdev)
{
struct device_node *ssi_np, *np = pdev->dev.of_node;
struct imx_phone_data *data;
struct platform_device *ssi_pdev;
int ret = 0, int_port, ext_port;
/*
Get the int and ext port from the dtb
*/
ret = of_property_read_u32(np, "mux-int-port", &int_port);
if (ret) {
dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
return ret;
}
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
if (ret) {
dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
return ret;
}
/*
* The port numbering in the hardware manual starts at 1, while
* the audmux API expects it starts at 0.
*/
int_port--;
ext_port--;
/*Configure the audmux*/
ret = imx_audmux_v2_configure_port(int_port,
IMX_AUDMUX_V2_PTCR_SYN |
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TFSDIR |
IMX_AUDMUX_V2_PTCR_TCLKDIR,
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
if (ret) {
dev_err(&pdev->dev, "audmux internal port setup failed\n");
return ret;
}
ret = imx_audmux_v2_configure_port(ext_port,
IMX_AUDMUX_V2_PTCR_SYN,
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
if (ret) {
dev_err(&pdev->dev, "audmux external port setup failed\n");
return ret;
}
//Get the i2s controller from dtb
ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
if (!ssi_np) {
dev_err(&pdev->dev, "phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}
//find the i2s node and get its pdev struct
ssi_pdev = of_find_device_by_node(ssi_np);
if (!ssi_pdev) {
dev_err(&pdev->dev, "failed to find SSI platform device\n");
ret = -EINVAL;
goto fail;
}
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
dev_err(&pdev->dev, "failed to allocate memory\n");
ret = -ENOMEM;
goto fail;
}
data->dai.name = "Phone HiFi";
data->dai.stream_name = "Phone Audio";
data->dai.codec_dai_name = "snd-soc-dummy-dai";
data->dai.codec_name = "snd-soc-dummy";
data->dai.cpu_of_node = ssi_np;
data->dai.platform_of_node = ssi_np;
data->dai.dai_fmt = SND_SOC_DAIFMT_DSP_A | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM;
data->card.dev = &pdev->dev;
data->card.num_links = 1;
data->card.dai_link = &data->dai;
data->pdev = platform_device_register_simple("phone", -1, NULL, 0);
if (IS_ERR(data->pdev)) {
ret = PTR_ERR(data->pdev);
dev_err(&pdev->dev, "register failed: %d\n", ret);
goto fail;
}
//Fill in the card details
ret = snd_soc_of_parse_card_name(&data->card, "model");
if (ret)
goto fail;
data->card.num_links = 1;
data->card.owner = THIS_MODULE;
data->card.dai_link = &data->dai;
platform_set_drvdata(pdev, &data->card);
snd_soc_card_set_drvdata(&data->card, data);
ret = snd_soc_register_card(&data->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret);
goto fail;
}
of_node_put(ssi_np);
return 0;
fail:
if (ssi_np)
of_node_put(ssi_np);
return ret;
}
static int imx_phone_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct imx_phone_data *data = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
platform_device_unregister(data->pdev);
return 0;
}
static const struct of_device_id imx_phone_dt_ids[] = {
{ .compatible = "fsl,imx-audio-phone", },
{ }
};
MODULE_DEVICE_TABLE(of, imx_phone_dt_ids);
static struct platform_driver imx_phone_driver = {
.driver = {
.name = "imx-audio-phone",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = imx_phone_dt_ids,
},
.probe = imx_phone_audio_probe,
.remove = imx_phone_audio_remove,
};
module_platform_driver(imx_phone_driver);
As said in my intro post the main problem I could detect seems to be that the call to fsl_ssi_set_dai_fmt() doesnt do anything when it should set the i2s format.
hello Christian,
sorry! probably there is something wrong with my understanding your audio application.
On your board, are you using "IMX6ULL SAI + SGTL5000" ?
if not, which i.MX processor are you using?
Have a nice day!
B.R,
Weidong
Hello Weidong,
we use an i.MX 6DualPlus (with Kernel 3.10.17).
The audio application looks the following basically:
SIMCOM7600 (snd-soc-dummy) + I2S (ssi1, fsl_ssi).
The code I posted above is from our own machine driver which is based upon the SGTL5000 machine driver.
The Kernel we use doesn't offer any implementation/code for SAI or simple-card.c.
The problem with our driver seems to be that the fsl_ssi side isn't set up properly, the format parameters are set (verified through debbuging) but don't seem to have any effect.
Regards
Christian
Hello Christian
OK, understood.
Your audio application is like this:
i.MX6DP SSI + DUMMY CODEC -- > Dummy Sound card.
platform driver should be : imx-ssi.c.
I tried to change some code on the basis of yours, see attachment, please!
probably it is not complete, your can try and debug it.
Have a nice day!
B.R,
Weidong
Hello Weidong,
the code you provided helped me in tracking down the main problem and finally fixing it.
The plattform driver used is still fsl_ssi (imx-ssi doesn't work with a device tree configuration as far as I know) but now it is setup correctly.
What was missing was the call to snd_soc_dai_set_fmt() in imx_hifi_hw_params(). This seems necessary because after the initialziation phase fsl_ssi reverts its fmt parameters every time audio data transfer resumes on the I2S device.
#include <linux/module.h>
#include <linux/of_platform.h>
#include <sound/soc.h>
#include <linux/of.h>
#include <../sound/soc/fsl/imx-audmux.h>
struct imx_phone_data {
struct snd_soc_dai_link dai;
struct snd_soc_card card;
struct platform_device *pdev;
};
struct imx_phone_data *data;
static int imx_hifi_startup(struct snd_pcm_substream *substream)
{
return 0;
}
static int imx_hifi_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
struct snd_soc_pcm_runtime *rtd = substream->private_data;
struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
struct device *dev = data->card.dev;
int ret = 0;
ret = snd_soc_dai_set_fmt(cpu_dai, data->dai.dai_fmt);
if (ret) {
dev_err(dev, "setting format for cpu dai failed\n");
return ret;
}
return 0;
}
static int imx_hifi_hw_free(struct snd_pcm_substream *substream)
{
return 0;
}
static struct snd_soc_ops imx_hifi_ops = {
.startup = imx_hifi_startup,
.hw_params = imx_hifi_hw_params,
.hw_free = imx_hifi_hw_free,
};
static int imx_phone_audio_probe(struct platform_device *pdev)
{
struct device_node *ssi_np, *np = pdev->dev.of_node;
struct platform_device *ssi_pdev;
int ret = 0, int_port, ext_port;
//struct platform_device *cpu_pdev;
/*
Get the int and ext port from the dtb
*/
ret = of_property_read_u32(np, "mux-int-port", &int_port);
if (ret) {
dev_err(&pdev->dev, "mux-int-port missing or invalid\n");
return ret;
}
ret = of_property_read_u32(np, "mux-ext-port", &ext_port);
if (ret) {
dev_err(&pdev->dev, "mux-ext-port missing or invalid\n");
return ret;
}
/*
* The port numbering in the hardware manual starts at 1, while
* the audmux API expects it starts at 0.
*/
int_port--;
ext_port--;
/*Configure the audmux*/
ret = imx_audmux_v2_configure_port(int_port,
IMX_AUDMUX_V2_PTCR_SYN |
IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
IMX_AUDMUX_V2_PTCR_TFSDIR |
IMX_AUDMUX_V2_PTCR_TCLKDIR,
IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port));
if (ret) {
dev_err(&pdev->dev, "audmux internal port setup failed\n");
return ret;
}
ret = imx_audmux_v2_configure_port(ext_port,
IMX_AUDMUX_V2_PTCR_SYN,
IMX_AUDMUX_V2_PDCR_RXDSEL(int_port));
if (ret) {
dev_err(&pdev->dev, "audmux external port setup failed\n");
return ret;
}
//Get the i2s controller from dtb
ssi_np = of_parse_phandle(pdev->dev.of_node, "ssi-controller", 0);
if (!ssi_np) {
dev_err(&pdev->dev, "phandle missing or invalid\n");
ret = -EINVAL;
goto fail;
}
//find the i2s node and get its pdev struct
ssi_pdev = of_find_device_by_node(ssi_np);
if (!ssi_pdev) {
dev_err(&pdev->dev, "failed to find SSI platform device\n");
ret = -EINVAL;
goto fail;
}
data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
if (!data) {
dev_err(&pdev->dev, "failed to allocate memory\n");
ret = -ENOMEM;
goto fail;
}
data->dai.name = "Phone HiFi";
data->dai.stream_name = "Phone Audio";
data->dai.codec_dai_name = "snd-soc-dummy-dai";
data->dai.codec_name = "snd-soc-dummy";
data->dai.cpu_dai_name = dev_name(&ssi_pdev->dev);
data->dai.ops = &imx_hifi_ops;
data->dai.cpu_of_node = ssi_np;
data->dai.platform_of_node = ssi_np;
data->dai.dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF |
SND_SOC_DAIFMT_CBM_CFM;
data->card.dev = &pdev->dev;
data->card.num_links = 1;
data->card.dai_link = &data->dai;
data->pdev = platform_device_register_simple("phone", -1, NULL, 0);
if (IS_ERR(data->pdev)) {
ret = PTR_ERR(data->pdev);
dev_err(&pdev->dev, "register failed: %d\n", ret);
goto fail;
}
//Fill in the card details
ret = snd_soc_of_parse_card_name(&data->card, "model");
if (ret)
goto fail;
data->card.num_links = 1;
data->card.owner = THIS_MODULE;
data->card.dai_link = &data->dai;
platform_set_drvdata(pdev, &data->card);
snd_soc_card_set_drvdata(&data->card, data);
ret = snd_soc_register_card(&data->card);
if (ret) {
dev_err(&pdev->dev, "snd_soc_register_card failed: %d\n", ret);
goto fail;
}
of_node_put(ssi_np);
return 0;
fail:
if (ssi_np)
of_node_put(ssi_np);
return ret;
}
static int imx_phone_audio_remove(struct platform_device *pdev)
{
struct snd_soc_card *card = platform_get_drvdata(pdev);
struct imx_phone_data *data = snd_soc_card_get_drvdata(card);
snd_soc_unregister_card(card);
platform_device_unregister(data->pdev);
return 0;
}
static const struct of_device_id imx_phone_dt_ids[] = {
{ .compatible = "fsl,imx-audio-phone", },
{ }
};
MODULE_DEVICE_TABLE(of, imx_phone_dt_ids);
static struct platform_driver imx_phone_driver = {
.driver = {
.name = "imx-audio-phone",
.owner = THIS_MODULE,
.pm = &snd_soc_pm_ops,
.of_match_table = imx_phone_dt_ids,
},
.probe = imx_phone_audio_probe,
.remove = imx_phone_audio_remove,
};
module_platform_driver(imx_phone_driver);
Regards
Christian
Hi Christian,
No matter whether you use real audio codec, your sound card driver must meet architecture Linux ALSA SoC driver:
1. platform driver(CPU side: SAI driver)
2. codec driver (codec driver)
3. machine driver. ( SAI + codec ---> sound card)
Otherwise, sai can't normally work.
According to your application, you don't use audio codec, but also need a dummy codec.
1. sai driver (fsl_sai.c)
2. dummy codec driver( soc-utils.c)
2. machine driver
sai + dummy-codec--->dummy sound card.
For the machine driver, you can use simple-card.c, or write one by yourself.
Hope above information is helpful for you.
Have a nice day!
B.R,
Weidong