diff mbox series

[6/7] ASoC: renesas: add MSIOF sound support

Message ID 87wmbu9may.wl-kuninori.morimoto.gx@renesas.com
State Superseded
Headers show
Series ASoC: add Renesas MSIOF sound driver | expand

Commit Message

Kuninori Morimoto April 9, 2025, 1:05 a.m. UTC
Renesas MSIOF (Clock-Synchronized Serial Interface with FIFO) can work as
both SPI and I2S. Adds MSIOF-I2S driver.

MSIOF-SPI/I2S are using same DT compatible properties.
MSIOF-I2S         uses Of-Graph for Audio-Graph-Card/Card2,
MSIOF-SPI doesn't use  Of-Graph.

Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
---
 sound/soc/renesas/Kconfig       |   7 +
 sound/soc/renesas/rcar/Makefile |   3 +
 sound/soc/renesas/rcar/msiof.c  | 579 ++++++++++++++++++++++++++++++++
 3 files changed, 589 insertions(+)
 create mode 100644 sound/soc/renesas/rcar/msiof.c

Comments

Krzysztof Kozlowski April 9, 2025, 6:38 a.m. UTC | #1
On 09/04/2025 03:05, Kuninori Morimoto wrote:
> +	priv->base = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(priv->base))
> +		return PTR_ERR(priv->base);
> +
> +	ret = devm_request_irq(dev, irq, msiof_interrupt, 0, dev_name(dev), priv);
> +	if (ret)
> +		return ret;
> +
> +	priv->dev	= dev;
> +	priv->phy_addr	= res->start;
> +
> +	spin_lock_init(&priv->lock);
> +	platform_set_drvdata(pdev, priv);
> +
> +	devm_pm_runtime_enable(dev);
> +
> +	ret = devm_snd_soc_register_component(dev, &msiof_component_driver,
> +					      &msiof_dai_driver, 1);
> +	if (ret)
> +		return ret;
> +
> +	dev_info(dev, "probed\n");

Drop. Driver should be silent on success and simple success messages are
useless. Core already gives you information that probe succeeded.

> +
> +	return ret;
> +}
> +
> +static const struct of_device_id msiof_of_match[] = {
> +	{ .compatible = "renesas,rcar-gen4-msiof", },
> +	{},
> +};
> +MODULE_DEVICE_TABLE(of, msiof_of_match);
> +
> +static struct platform_driver msiof_driver = {
> +	.driver 	= {
> +		.name	= "msiof-pcm-audio",
> +		.of_match_table = msiof_of_match,
> +	},
> +	.probe		= msiof_probe,
> +};
> +module_platform_driver(msiof_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_DESCRIPTION("Renesas R-Car MSIOF I2S audio driver");
> +MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
> +MODULE_ALIAS("platform:msiof-pcm-audio");

You should not need MODULE_ALIAS() in normal cases. If you need it,
usually it means your device ID table is wrong (e.g. misses either
entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is not a substitute
for incomplete ID table.




Best regards,
Krzysztof
Geert Uytterhoeven April 9, 2025, 7:28 a.m. UTC | #2
Hi Morimoto-san,

On Wed, 9 Apr 2025 at 03:05, Kuninori Morimoto
<kuninori.morimoto.gx@renesas.com> wrote:
> Renesas MSIOF (Clock-Synchronized Serial Interface with FIFO) can work as
> both SPI and I2S. Adds MSIOF-I2S driver.
>
> MSIOF-SPI/I2S are using same DT compatible properties.
> MSIOF-I2S         uses Of-Graph for Audio-Graph-Card/Card2,
> MSIOF-SPI doesn't use  Of-Graph.
>
> Signed-off-by: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>

Thanks for your patch!

> --- a/sound/soc/renesas/Kconfig
> +++ b/sound/soc/renesas/Kconfig
> @@ -46,6 +46,13 @@ config SND_SOC_RCAR
>         help
>           This option enables R-Car SRU/SCU/SSIU/SSI sound support
>
> +config SND_SOC_MSIOF
> +       tristate "R-Car series MSIOF support"
> +       depends on OF

depends on ARCH_RENESAS || COMPILE_TEST

> +       select SND_DMAENGINE_PCM
> +       help
> +         This option enables R-Car MSIOF sound support
> +
>  config SND_SOC_RZ
>         tristate "RZ/G2L series SSIF-2 support"
>         depends on ARCH_RZG2L || COMPILE_TEST
> diff --git a/sound/soc/renesas/rcar/Makefile b/sound/soc/renesas/rcar/Makefile
> index 45eb875a912a..3a2c875595bd 100644
> --- a/sound/soc/renesas/rcar/Makefile
> +++ b/sound/soc/renesas/rcar/Makefile
> @@ -1,3 +1,6 @@
>  # SPDX-License-Identifier: GPL-2.0
>  snd-soc-rcar-y         := core.o gen.o dma.o adg.o ssi.o ssiu.o src.o ctu.o mix.o dvc.o cmd.o debugfs.o
>  obj-$(CONFIG_SND_SOC_RCAR)     += snd-soc-rcar.o
> +
> +snd-soc-msiof-y                        := msiof.o
> +obj-$(CONFIG_SND_SOC_MSIOF)    += snd-soc-msiof.o
> diff --git a/sound/soc/renesas/rcar/msiof.c b/sound/soc/renesas/rcar/msiof.c
> new file mode 100644
> index 000000000000..de8de3468402
> --- /dev/null
> +++ b/sound/soc/renesas/rcar/msiof.c
> @@ -0,0 +1,579 @@
> +// SPDX-License-Identifier: GPL-2.0
> +//
> +// Renesas R-Car MSIOF (Clock-Synchronized Serial Interface with FIFO) I2S driver
> +//
> +// Copyright (C) 2025 Renesas Solutions Corp.
> +// Author: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
> +//
> +
> +/*
> + * [NOTE]
> + *
> + * This driver doesn't support Clock/Frame Provider Mode
> + *
> + * Basically MSIOF is created for SPI, but we can use it as I2S (Sound). Because of it, when we use
> + * it as I2S (Sound) with Provider Mode, we need to send dummy TX data even though it is used for
> + * RX. Because SPI HW needs TX Clock/Frame output for RX purpose also. It makes driver code complex.
> + *
> + * And when we use MSIOF (Sound) as Provider Mode, the clock source is [MSO clock] (= 133.33MHz)
> + * SoC internal clock. It is not for 48kHz/44.1kHz base clock. Thus the output/input will not be
> + * accurate sound.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_dma.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <sound/dmaengine_pcm.h>
> +#include <sound/soc.h>
> +
> +/* register */
> +#define SITMDR1                0x00
> +#define SITMDR2                0x04
> +#define SITMDR3                0x08
> +#define SIRMDR1                0x10
> +#define SIRMDR2                0x14
> +#define SIRMDR3                0x18
> +#define SITSCR         0x20
> +#define SICTR          0x28
> +#define SIFCTR         0x30
> +#define SISTR          0x40
> +#define SIIER          0x44
> +#define SITFDR         0x50
> +#define SIRFDR         0x60

[...]

Perhaps the register definitions can be shared with spi-sh-msiof.c,
by extracting them into a shared header file?

Note that I have already converted drivers/spi/spi-sh-msiof.c locally
to use FIELD_PREP() (which requires changes to some macros), so we
may want to implement the sharing later.

> +static int msiof_hw_start(struct snd_soc_component *component,
> +                         struct snd_pcm_substream *substream, int cmd)
> +{
> +       struct msiof_priv *priv = snd_soc_component_get_drvdata(component);
> +       struct snd_pcm_runtime *runtime = substream->runtime;
> +       int is_play = msiof_is_play(substream);
> +       int width = snd_pcm_format_width(runtime->format);
> +       u32 val;
> +
> +       /*
> +        * see
> +        *      Datasheet 109.3.6 [Transmit and Receive Procedures]
> +        *
> +        *      TX: Fig 109.14  - Fig 109.23
> +        *      RX: Fig 109.15
> +        */
> +
> +       /* reset errors */
> +       priv->err_syc[substream->stream] =
> +       priv->err_ovf[substream->stream] =
> +       priv->err_udf[substream->stream] = 0;
> +
> +       /* SITMDRx */
> +       if (is_play) {
> +               val = PCON | SYNCMD_LR | SYNCAC | TXSTP;
> +               if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
> +                       val |= DTDL_1;
> +
> +               msiof_write(priv, SITMDR1, val);
> +
> +               val = BITLEN1(width);
> +               msiof_write(priv, SITMDR2, val | GRP);
> +               msiof_write(priv, SITMDR3, val);
> +

Don't you have to initialize SITMDR[123] unconditionally, as reception
requires transmitting dummy data on R-Car (cfr. SPI_CONTROLLER_MUST_TX)?

> +       }
> +       /* SIRMDRx */
> +       else {
> +               val = SYNCMD_LR | SYNCAC;
> +               if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
> +                       val |= DTDL_1;
> +
> +               msiof_write(priv, SIRMDR1, val);
> +
> +               val = BITLEN1(width);
> +               msiof_write(priv, SIRMDR2, val | GRP);
> +               msiof_write(priv, SIRMDR3, val);
> +       }
> +
> +       /* SIIER */
> +       if (is_play)
> +               val = TDREQE | TDMAE | SISTR_ERR_TX;
> +       else
> +               val = RDREQE | RDMAE | SISTR_ERR_RX;

Likewise for transmit control flags.

> +       msiof_update(priv, SIIER, val, val);
> +
> +       /* SICTR */
> +       if (is_play)
> +               val = TXE | TEDG;
> +       else
> +               val = RXE | REDG;

Likewise,

> +       msiof_update_and_wait(priv, SICTR, val, val, val);
> +
> +       msiof_status_clear(priv);
> +
> +       /* Start DMAC */
> +       snd_dmaengine_pcm_trigger(substream, cmd);
> +
> +       return 0;
> +}
> +
> +static int msiof_hw_stop(struct snd_soc_component *component,
> +                        struct snd_pcm_substream *substream, int cmd)
> +{
> +       struct msiof_priv *priv = snd_soc_component_get_drvdata(component);
> +       struct device *dev = component->dev;
> +       int is_play = msiof_is_play(substream);
> +       u32 val;
> +
> +       /* SIIER */
> +       if (is_play)
> +               val = TDREQE | TDMAE | SISTR_ERR_TX;
> +       else
> +               val = RDREQE | RDMAE | SISTR_ERR_RX;

Likewise.

> +       msiof_update(priv, SIIER, val, 0);
> +
> +       /* Stop DMAC */
> +       snd_dmaengine_pcm_trigger(substream, cmd);
> +
> +       /* SICTR */
> +       if (is_play)
> +               val = TXE;
> +       else
> +               val = RXE;

Likewise.

> +       msiof_update_and_wait(priv, SICTR, val, 0, 0);
> +
> +       /* indicate error status if exist */
> +       if (priv->err_syc[substream->stream] ||
> +           priv->err_ovf[substream->stream] ||
> +           priv->err_udf[substream->stream])
> +               dev_warn(dev, "FSERR(%s) = %d, FOVF = %d, FUDF = %d\n",
> +                        snd_pcm_direction_name(substream->stream),
> +                        priv->err_syc[substream->stream],
> +                        priv->err_ovf[substream->stream],
> +                        priv->err_udf[substream->stream]);
> +
> +       return 0;
> +}

> +static int msiof_probe(struct platform_device *pdev)
> +{
> +       struct msiof_priv *priv;
> +       struct device *dev = &pdev->dev;
> +       struct resource *res;
> +       struct device_node *port;
> +       int irq, ret;
> +
> +       /* Check MSIOF as Sound mode or SPI mode */
> +       port = of_graph_get_next_port(dev->of_node, NULL);
> +       if (!port)
> +               return -ENODEV;
> +       of_node_put(port);

Just wondering: don't you need to use port? Or is that handled
elsewhere, in common sound code?

Gr{oetje,eeting}s,

                        Geert
Kuninori Morimoto April 9, 2025, 11:25 p.m. UTC | #3
Hi Krzysztof, Mark, Iwai-san

> > +	dev_info(dev, "probed\n");
> 
> Drop. Driver should be silent on success and simple success messages are
> useless. Core already gives you information that probe succeeded.

What does "Core" mean here ??

> > +MODULE_ALIAS("platform:msiof-pcm-audio");
> 
> You should not need MODULE_ALIAS() in normal cases. If you need it,
> usually it means your device ID table is wrong (e.g. misses either
> entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is not a substitute
> for incomplete ID table.

Iwai-san, Mark, do you have any comment about this ?
Almost all ALSA drivers are using it

	> git grep MODULE_ALIAS sound | wc -l
	249

Thank you for your help !!

Best regards
---
Kuninori Morimoto
Kuninori Morimoto April 9, 2025, 11:45 p.m. UTC | #4
Hi Geert

Thank you for your review

> > +config SND_SOC_MSIOF
> > +       tristate "R-Car series MSIOF support"
> > +       depends on OF
> 
> depends on ARCH_RENESAS || COMPILE_TEST

Ah, yes indeed. Will add in v2

> Perhaps the register definitions can be shared with spi-sh-msiof.c,
> by extracting them into a shared header file?
> 
> Note that I have already converted drivers/spi/spi-sh-msiof.c locally
> to use FIELD_PREP() (which requires changes to some macros), so we
> may want to implement the sharing later.

Yes 100% I can agree about this !
I'm happy to sharing it, but it will be next step

> > +       /* SITMDRx */
> > +       if (is_play) {
> > +               val = PCON | SYNCMD_LR | SYNCAC | TXSTP;
> > +               if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
> > +                       val |= DTDL_1;
> > +
> > +               msiof_write(priv, SITMDR1, val);
> > +
> > +               val = BITLEN1(width);
> > +               msiof_write(priv, SITMDR2, val | GRP);
> > +               msiof_write(priv, SITMDR3, val);
> > +
> 
> Don't you have to initialize SITMDR[123] unconditionally, as reception
> requires transmitting dummy data on R-Car (cfr. SPI_CONTROLLER_MUST_TX)?

Good catch, but I added 1 restriction for MSIOF-I2S mode.
I have explained it on top of this driver. The restriction is
"MSIOF-I2S doesn't work as Clock/Frame Provider Mode".
So, dummy transmit for RX is not needed/assumed.
I think it is one of big-diff between MSIOF-SPI ?

/*
 * [NOTE]
 *
 * This driver doesn't support Clock/Frame Provider Mode
 *
 * Basically MSIOF is created for SPI, but we can use it as I2S (Sound). Because of it, when we use
 * it as I2S (Sound) with Provider Mode, we need to send dummy TX data even though it is used for
 * RX. Because SPI HW needs TX Clock/Frame output for RX purpose also. It makes driver code complex.
 *
 * And when we use MSIOF (Sound) as Provider Mode, the clock source is [MSO clock] (= 133.33MHz)
 * SoC internal clock. It is not for 48kHz/44.1kHz base clock. Thus the output/input will not be
 * accurate sound.
 */

> > +static int msiof_probe(struct platform_device *pdev)
> > +{
> > +       struct msiof_priv *priv;
> > +       struct device *dev = &pdev->dev;
> > +       struct resource *res;
> > +       struct device_node *port;
> > +       int irq, ret;
> > +
> > +       /* Check MSIOF as Sound mode or SPI mode */
> > +       port = of_graph_get_next_port(dev->of_node, NULL);
> > +       if (!port)
> > +               return -ENODEV;
> > +       of_node_put(port);
> 
> Just wondering: don't you need to use port? Or is that handled
> elsewhere, in common sound code?

"ports/port" will be handled by "Sound Card" driver
(= Audio Graph Card/Card2), not MSIOF driver.

Thank you for your help !!

Best regards
---
Kuninori Morimoto
Kuninori Morimoto April 10, 2025, 2:37 a.m. UTC | #5
Hi Geert, again

> > > +config SND_SOC_MSIOF
> > > +       tristate "R-Car series MSIOF support"
> > > +       depends on OF
> > 
> > depends on ARCH_RENESAS || COMPILE_TEST
> 
> Ah, yes indeed. Will add in v2

Renesas category Sound drivers are under below menu.
So, it is not needed on each drivers.

menu "SoC Audio support for Renesas SoCs"
	depends on SUPERH || ARCH_RENESAS || COMPILE_TEST

Thank you for your help !!

Best regards
---
Kuninori Morimoto
Krzysztof Kozlowski April 10, 2025, 5:45 a.m. UTC | #6
On 10/04/2025 01:25, Kuninori Morimoto wrote:
> 
> Hi Krzysztof, Mark, Iwai-san
> 
>>> +	dev_info(dev, "probed\n");
>>
>> Drop. Driver should be silent on success and simple success messages are
>> useless. Core already gives you information that probe succeeded.
> 
> What does "Core" mean here ??

Core of Linux.

> 
>>> +MODULE_ALIAS("platform:msiof-pcm-audio");
>>
>> You should not need MODULE_ALIAS() in normal cases. If you need it,
>> usually it means your device ID table is wrong (e.g. misses either
>> entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is not a substitute
>> for incomplete ID table.
> 
> Iwai-san, Mark, do you have any comment about this ?
> Almost all ALSA drivers are using it
> 
> 	> git grep MODULE_ALIAS sound | wc -l
> 	249


What do you need it for? You already have ID table.

Just because drivers need it, is not a justification that you need. If
other drivers have poor code, it's okay to do the same?


Best regards,
Krzysztof
Kuninori Morimoto April 10, 2025, 6:29 a.m. UTC | #7
Hi Krzysztof, Mark, Takashi-san

> >>> +MODULE_ALIAS("platform:msiof-pcm-audio");
> >>
> >> You should not need MODULE_ALIAS() in normal cases. If you need it,
> >> usually it means your device ID table is wrong (e.g. misses either
> >> entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is not a substitute
> >> for incomplete ID table.
> > 
> > Iwai-san, Mark, do you have any comment about this ?
> > Almost all ALSA drivers are using it
> > 
> > 	> git grep MODULE_ALIAS sound | wc -l
> > 	249
> 
> 
> What do you need it for? You already have ID table.
> 
> Just because drivers need it, is not a justification that you need. If
> other drivers have poor code, it's okay to do the same?

In my understanding, it is needed for userspace (and the macro is for it ?)
but I'm not familiar with userspace.
Mark, Takashi-san ?

Thank you for your help !!

Best regards
---
Kuninori Morimoto
Krzysztof Kozlowski April 10, 2025, 6:33 a.m. UTC | #8
On 10/04/2025 08:29, Kuninori Morimoto wrote:
> 
> Hi Krzysztof, Mark, Takashi-san
> 
>>>>> +MODULE_ALIAS("platform:msiof-pcm-audio");
>>>>
>>>> You should not need MODULE_ALIAS() in normal cases. If you need it,
>>>> usually it means your device ID table is wrong (e.g. misses either
>>>> entries or MODULE_DEVICE_TABLE()). MODULE_ALIAS() is not a substitute
>>>> for incomplete ID table.
>>>
>>> Iwai-san, Mark, do you have any comment about this ?
>>> Almost all ALSA drivers are using it
>>>
>>> 	> git grep MODULE_ALIAS sound | wc -l
>>> 	249
>>
>>
>> What do you need it for? You already have ID table.
>>
>> Just because drivers need it, is not a justification that you need. If
>> other drivers have poor code, it's okay to do the same?
> 
> In my understanding, it is needed for userspace (and the macro is for it ?)
> but I'm not familiar with userspace.

It creates alias and you already have the alias from OF ID table, so
which userspace depends on this alias? Using other drivers as an example
is not helping, because for several of them such alias is necessary
since they do not have ID table. But you have. So again - my comment
stays valid or please bring the explanation which component relies on
this alias and cannot be switched to aliases coming from OF or platform
tables.

I am kind of repeating here myself, but I gave you the answer that you
do not need it.

Best regards,
Krzysztof
Geert Uytterhoeven April 10, 2025, 7:39 a.m. UTC | #9
Hi Morimoto-san,

On Thu, 10 Apr 2025 at 04:37, Kuninori Morimoto
<kuninori.morimoto.gx@renesas.com> wrote:
> > > > +config SND_SOC_MSIOF
> > > > +       tristate "R-Car series MSIOF support"
> > > > +       depends on OF
> > >
> > > depends on ARCH_RENESAS || COMPILE_TEST
> >
> > Ah, yes indeed. Will add in v2
>
> Renesas category Sound drivers are under below menu.
> So, it is not needed on each drivers.
>
> menu "SoC Audio support for Renesas SoCs"
>         depends on SUPERH || ARCH_RENESAS || COMPILE_TEST

Thanks, I should have checked that...

Gr{oetje,eeting}s,

                        Geert
Geert Uytterhoeven April 10, 2025, 7:47 a.m. UTC | #10
Hi Morimoto-san,

On Thu, 10 Apr 2025 at 01:45, Kuninori Morimoto
<kuninori.morimoto.gx@renesas.com> wrote:
> > > +       /* SITMDRx */
> > > +       if (is_play) {
> > > +               val = PCON | SYNCMD_LR | SYNCAC | TXSTP;
> > > +               if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
> > > +                       val |= DTDL_1;
> > > +
> > > +               msiof_write(priv, SITMDR1, val);
> > > +
> > > +               val = BITLEN1(width);
> > > +               msiof_write(priv, SITMDR2, val | GRP);
> > > +               msiof_write(priv, SITMDR3, val);
> > > +
> >
> > Don't you have to initialize SITMDR[123] unconditionally, as reception
> > requires transmitting dummy data on R-Car (cfr. SPI_CONTROLLER_MUST_TX)?
>
> Good catch, but I added 1 restriction for MSIOF-I2S mode.
> I have explained it on top of this driver. The restriction is
> "MSIOF-I2S doesn't work as Clock/Frame Provider Mode".
> So, dummy transmit for RX is not needed/assumed.
> I think it is one of big-diff between MSIOF-SPI ?

IC.  Being just a mortal sound-noob, I didn't know what "Clock/Frame
Provider Mode" means ;-) Oh, now I understand. I had missed
completely that you are running MSIOF in slave mode. So everything
should be fine.

And

    /* SITSCR */
    #define SITSCR_V(p, d) ((p << 8) + d)

is unused, and can be removed.

Gr{oetje,eeting}s,

                        Geert
Kuninori Morimoto April 10, 2025, 11:04 p.m. UTC | #11
Hi Geert

> > > > > +config SND_SOC_MSIOF
> > > > > +       tristate "R-Car series MSIOF support"
> > > > > +       depends on OF
> > > >
> > > > depends on ARCH_RENESAS || COMPILE_TEST
> > >
> > > Ah, yes indeed. Will add in v2
> >
> > Renesas category Sound drivers are under below menu.
> > So, it is not needed on each drivers.
> >
> > menu "SoC Audio support for Renesas SoCs"
> >         depends on SUPERH || ARCH_RENESAS || COMPILE_TEST
> 
> Thanks, I should have checked that...

No problem.
Thank you for your review anyway

Best regards
---
Kuninori Morimoto
Kuninori Morimoto April 10, 2025, 11:08 p.m. UTC | #12
Hi Geert

> > > Don't you have to initialize SITMDR[123] unconditionally, as reception
> > > requires transmitting dummy data on R-Car (cfr. SPI_CONTROLLER_MUST_TX)?
> >
> > Good catch, but I added 1 restriction for MSIOF-I2S mode.
> > I have explained it on top of this driver. The restriction is
> > "MSIOF-I2S doesn't work as Clock/Frame Provider Mode".
> > So, dummy transmit for RX is not needed/assumed.
> > I think it is one of big-diff between MSIOF-SPI ?
> 
> IC.  Being just a mortal sound-noob, I didn't know what "Clock/Frame
> Provider Mode" means ;-) Oh, now I understand. I had missed
> completely that you are running MSIOF in slave mode. So everything
> should be fine.

ASoC is now using Provider/Consumer instead of Master/Slave.
Veteran engineers are not familiar with it :)

>     /* SITSCR */
>     #define SITSCR_V(p, d) ((p << 8) + d)
> 
> is unused, and can be removed.

OK, Thanks.
Will remove in v2

Thank you for your help !!

Best regards
---
Kuninori Morimoto
diff mbox series

Patch

diff --git a/sound/soc/renesas/Kconfig b/sound/soc/renesas/Kconfig
index cb01fb36355f..dabf02a955ca 100644
--- a/sound/soc/renesas/Kconfig
+++ b/sound/soc/renesas/Kconfig
@@ -46,6 +46,13 @@  config SND_SOC_RCAR
 	help
 	  This option enables R-Car SRU/SCU/SSIU/SSI sound support
 
+config SND_SOC_MSIOF
+	tristate "R-Car series MSIOF support"
+	depends on OF
+	select SND_DMAENGINE_PCM
+	help
+	  This option enables R-Car MSIOF sound support
+
 config SND_SOC_RZ
 	tristate "RZ/G2L series SSIF-2 support"
 	depends on ARCH_RZG2L || COMPILE_TEST
diff --git a/sound/soc/renesas/rcar/Makefile b/sound/soc/renesas/rcar/Makefile
index 45eb875a912a..3a2c875595bd 100644
--- a/sound/soc/renesas/rcar/Makefile
+++ b/sound/soc/renesas/rcar/Makefile
@@ -1,3 +1,6 @@ 
 # SPDX-License-Identifier: GPL-2.0
 snd-soc-rcar-y		:= core.o gen.o dma.o adg.o ssi.o ssiu.o src.o ctu.o mix.o dvc.o cmd.o debugfs.o
 obj-$(CONFIG_SND_SOC_RCAR)	+= snd-soc-rcar.o
+
+snd-soc-msiof-y			:= msiof.o
+obj-$(CONFIG_SND_SOC_MSIOF)	+= snd-soc-msiof.o
diff --git a/sound/soc/renesas/rcar/msiof.c b/sound/soc/renesas/rcar/msiof.c
new file mode 100644
index 000000000000..de8de3468402
--- /dev/null
+++ b/sound/soc/renesas/rcar/msiof.c
@@ -0,0 +1,579 @@ 
+// SPDX-License-Identifier: GPL-2.0
+//
+// Renesas R-Car MSIOF (Clock-Synchronized Serial Interface with FIFO) I2S driver
+//
+// Copyright (C) 2025 Renesas Solutions Corp.
+// Author: Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>
+//
+
+/*
+ * [NOTE]
+ *
+ * This driver doesn't support Clock/Frame Provider Mode
+ *
+ * Basically MSIOF is created for SPI, but we can use it as I2S (Sound). Because of it, when we use
+ * it as I2S (Sound) with Provider Mode, we need to send dummy TX data even though it is used for
+ * RX. Because SPI HW needs TX Clock/Frame output for RX purpose also. It makes driver code complex.
+ *
+ * And when we use MSIOF (Sound) as Provider Mode, the clock source is [MSO clock] (= 133.33MHz)
+ * SoC internal clock. It is not for 48kHz/44.1kHz base clock. Thus the output/input will not be
+ * accurate sound.
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_dma.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/soc.h>
+
+/* register */
+#define SITMDR1		0x00
+#define SITMDR2		0x04
+#define SITMDR3		0x08
+#define SIRMDR1		0x10
+#define SIRMDR2		0x14
+#define SIRMDR3		0x18
+#define SITSCR		0x20
+#define SICTR		0x28
+#define SIFCTR		0x30
+#define SISTR		0x40
+#define SIIER		0x44
+#define SITFDR		0x50
+#define SIRFDR		0x60
+
+/* SITMDR1/ SIRMDR1 */
+#define TRMD		(1 << 31)		/* Transfer Mode (1 = Master mode) */
+#define PCON		(1 << 30)		/* Transfer Signal Connection */
+#define SYNCMD_SPI	(2 << 28)		/* Level mode/SPI */
+#define SYNCMD_LR	(3 << 28)		/* L/R mode */
+#define SYNCAC		(1 << 25)		/* Sync Polarity (Active-low) */
+#define DTDL_1		(1 << 20)		/* 1-clock-cycle delay */
+#define TXSTP		(1 <<  0)		/* Transmission/Reception Stop on FIFO */
+
+/* SITMDR2 and SIRMDR2 */
+#define BITLEN1(x)	(((x) - 1) << 24)	/* Data Size (8-32 bits) */
+#define WDLEN1(x)	(((x) - 1) << 16)	/* Word Count (1-64/256 (SH, A1))) */
+#define GRP		(1 << 30)		/* Group count */
+
+/* SITSCR */
+#define SITSCR_V(p, d)	((p << 8) + d)
+
+/* SICTR */
+#define TEDG		(1 << 27)		/* Transmit Timing (1 = falling edge) */
+#define REDG		(1 << 26)		/* Receive  Timing (1 = rising  edge) */
+#define TSCKE		(1 << 15)		/* Transmit Serial Clock Output Enable */
+#define TFSE		(1 << 14)		/* Transmit Frame Sync Signal Output Enable */
+#define TXE		(1 <<  9)		/* Transmit Enable */
+#define RXE		(1 <<  8)		/* Receive Enable */
+#define TXRST		(1 <<  1)		/* Transmit Reset */
+#define RXRST		(1 <<  0)		/* Receive Reset */
+
+/* SISTR */
+#define TFEMP		(1 << 29)		/* Transmit FIFO Empty */
+#define TDREQ		(1 << 28)		/* Transmit Data Transfer Request */
+#define TEOF		(1 << 23)		/* Frame Transmission End */
+#define TFSERR		(1 << 21)		/* Transmit Frame Synchronization Error */
+#define TFOVF		(1 << 20)		/* Transmit FIFO Overflow */
+#define TFUDF		(1 << 19)		/* Transmit FIFO Underflow */
+#define RFFUL		(1 << 13)		/* Receive FIFO Full */
+#define RDREQ		(1 << 12)		/* Receive Data Transfer Request */
+#define REOF		(1 <<  7)		/* Frame Reception End */
+#define RFSERR		(1 <<  5)		/* Receive Frame Synchronization Error */
+#define RFUDF		(1 <<  4)		/* Receive FIFO Underflow */
+#define RFOVF		(1 <<  3)		/* Receive FIFO Overflow */
+#define SISTR_ERR_TX	(TFSERR | TFOVF | TFUDF)
+#define SISTR_ERR_RX	(RFSERR | RFOVF | RFUDF)
+#define SISTR_ERR	(SISTR_ERR_TX | SISTR_ERR_RX)
+
+/* SIIER */
+#define TDMAE		(1 << 31)		/* Transmit Data DMA Transfer Req. Enable */
+#define TDREQE		(1 << 28)		/* Transmit Data Transfer Request Enable */
+#define RDMAE		(1 << 15)		/* Receive Data DMA Transfer Req. Enable */
+#define RDREQE		(1 << 12)		/* Receive Data Transfer Request Enable */
+
+/*
+ * The data on memory in 24bit case is located at <rigth> side
+ *	[  xxxxxx]
+ *	[  xxxxxx]
+ *	[  xxxxxx]
+ *
+ * HW assuming signal in 24bit case is located at <left> side
+ *	---+        +--------+
+ *	   +--------+        +--------+...
+ *	   [xxxxx  ][xxxxx  ][xxxxx  ]
+ *
+ * When we use 24bit data, it will be transfered via 32bit width via DMA,
+ * and MSIOF/DMA doesn't support data shift, we can't use 24bit data correctly.
+ * There is no such issue on 16/32bit data case.
+ */
+#define MSIOF_RATES	SNDRV_PCM_RATE_8000_192000
+#define MSIOF_FMTS	(SNDRV_PCM_FMTBIT_S16_LE |\
+			 SNDRV_PCM_FMTBIT_S32_LE)
+
+struct msiof_priv {
+	struct device *dev;
+	struct snd_pcm_substream *substream[SNDRV_PCM_STREAM_LAST + 1];
+	spinlock_t lock;
+	void __iomem *base;
+	resource_size_t phy_addr;
+
+	/* for error */
+	int err_syc[SNDRV_PCM_STREAM_LAST + 1];
+	int err_ovf[SNDRV_PCM_STREAM_LAST + 1];
+	int err_udf[SNDRV_PCM_STREAM_LAST + 1];
+
+	/* bit field */
+	u32 flags;
+#define MSIOF_FLAGS_NEED_DELAY		(1 << 0)
+};
+#define msiof_flag_has(priv, flag)	(priv->flags &  flag)
+#define msiof_flag_set(priv, flag)	(priv->flags |= flag)
+
+#define msiof_is_play(substream)	(substream)->stream == SNDRV_PCM_STREAM_PLAYBACK
+#define msiof_read(priv, reg)		ioread32((priv)->base + reg)
+#define msiof_write(priv, reg, val)	iowrite32(val, (priv)->base + reg)
+#define msiof_status_clear(priv)	msiof_write(priv, SISTR, SISTR_ERR)
+
+static void msiof_update(struct msiof_priv *priv, u32 reg, u32 mask, u32 val)
+{
+	u32 old = msiof_read(priv, reg);
+	u32 new = (old & ~mask) | (val & mask);
+
+	if (old != new)
+		msiof_write(priv, reg, new);
+}
+
+static void msiof_update_and_wait(struct msiof_priv *priv, u32 reg, u32 mask, u32 val, u32 expect)
+{
+	u32 data;
+	int ret;
+
+	msiof_update(priv, reg, mask, val);
+
+	ret = readl_poll_timeout_atomic(priv->base + reg, data,
+					(data & mask) == expect, 1, 128);
+	if (ret)
+		dev_warn(priv->dev, "write timeout [0x%02x] 0x%08x / 0x%08x\n",
+			 reg, data, expect);
+}
+
+static int msiof_hw_start(struct snd_soc_component *component,
+			  struct snd_pcm_substream *substream, int cmd)
+{
+	struct msiof_priv *priv = snd_soc_component_get_drvdata(component);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int is_play = msiof_is_play(substream);
+	int width = snd_pcm_format_width(runtime->format);
+	u32 val;
+
+	/*
+	 * see
+	 *	Datasheet 109.3.6 [Transmit and Receive Procedures]
+	 *
+	 *	TX: Fig 109.14	- Fig 109.23
+	 *	RX: Fig 109.15
+	 */
+
+	/* reset errors */
+	priv->err_syc[substream->stream] =
+	priv->err_ovf[substream->stream] =
+	priv->err_udf[substream->stream] = 0;
+
+	/* SITMDRx */
+	if (is_play) {
+		val = PCON | SYNCMD_LR | SYNCAC | TXSTP;
+		if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
+			val |= DTDL_1;
+
+		msiof_write(priv, SITMDR1, val);
+
+		val = BITLEN1(width);
+		msiof_write(priv, SITMDR2, val | GRP);
+		msiof_write(priv, SITMDR3, val);
+
+	}
+	/* SIRMDRx */
+	else {
+		val = SYNCMD_LR | SYNCAC;
+		if (msiof_flag_has(priv, MSIOF_FLAGS_NEED_DELAY))
+			val |= DTDL_1;
+
+		msiof_write(priv, SIRMDR1, val);
+
+		val = BITLEN1(width);
+		msiof_write(priv, SIRMDR2, val | GRP);
+		msiof_write(priv, SIRMDR3, val);
+	}
+
+	/* SIIER */
+	if (is_play)
+		val = TDREQE | TDMAE | SISTR_ERR_TX;
+	else
+		val = RDREQE | RDMAE | SISTR_ERR_RX;
+	msiof_update(priv, SIIER, val, val);
+
+	/* SICTR */
+	if (is_play)
+		val = TXE | TEDG;
+	else
+		val = RXE | REDG;
+	msiof_update_and_wait(priv, SICTR, val, val, val);
+
+	msiof_status_clear(priv);
+
+	/* Start DMAC */
+	snd_dmaengine_pcm_trigger(substream, cmd);
+
+	return 0;
+}
+
+static int msiof_hw_stop(struct snd_soc_component *component,
+			 struct snd_pcm_substream *substream, int cmd)
+{
+	struct msiof_priv *priv = snd_soc_component_get_drvdata(component);
+	struct device *dev = component->dev;
+	int is_play = msiof_is_play(substream);
+	u32 val;
+
+	/* SIIER */
+	if (is_play)
+		val = TDREQE | TDMAE | SISTR_ERR_TX;
+	else
+		val = RDREQE | RDMAE | SISTR_ERR_RX;
+	msiof_update(priv, SIIER, val, 0);
+
+	/* Stop DMAC */
+	snd_dmaengine_pcm_trigger(substream, cmd);
+
+	/* SICTR */
+	if (is_play)
+		val = TXE;
+	else
+		val = RXE;
+	msiof_update_and_wait(priv, SICTR, val, 0, 0);
+
+	/* indicate error status if exist */
+	if (priv->err_syc[substream->stream] ||
+	    priv->err_ovf[substream->stream] ||
+	    priv->err_udf[substream->stream])
+		dev_warn(dev, "FSERR(%s) = %d, FOVF = %d, FUDF = %d\n",
+			 snd_pcm_direction_name(substream->stream),
+			 priv->err_syc[substream->stream],
+			 priv->err_ovf[substream->stream],
+			 priv->err_udf[substream->stream]);
+
+	return 0;
+}
+
+static int msiof_dai_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct msiof_priv *priv = snd_soc_dai_get_drvdata(dai);
+
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	/* it supports Clock/Frame consumer mode only */
+	case SND_SOC_DAIFMT_BC_FC:
+		break;
+	/* others are error */
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	/* it supports NB_NF only */
+	case SND_SOC_DAIFMT_NB_NF:
+	default:
+		break;
+	/* others are error */
+	case SND_SOC_DAIFMT_NB_IF:
+	case SND_SOC_DAIFMT_IB_NF:
+	case SND_SOC_DAIFMT_IB_IF:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		msiof_flag_set(priv, MSIOF_FLAGS_NEED_DELAY);
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*
+ * Select below from Sound Card, not auto
+ *	SND_SOC_DAIFMT_CBC_CFC
+ *	SND_SOC_DAIFMT_CBP_CFP
+ */
+static const u64 msiof_dai_formats = SND_SOC_POSSIBLE_DAIFMT_I2S	|
+				     SND_SOC_POSSIBLE_DAIFMT_LEFT_J	|
+				     SND_SOC_POSSIBLE_DAIFMT_NB_NF;
+
+static const struct snd_soc_dai_ops msiof_dai_ops = {
+	.set_fmt			= msiof_dai_set_fmt,
+	.auto_selectable_formats	= &msiof_dai_formats,
+	.num_auto_selectable_formats	= 1,
+};
+
+static struct snd_soc_dai_driver msiof_dai_driver = {
+	.name = "msiof-dai",
+	.playback = {
+		.rates		= MSIOF_RATES,
+		.formats	= MSIOF_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.capture = {
+		.rates		= MSIOF_RATES,
+		.formats	= MSIOF_FMTS,
+		.channels_min	= 2,
+		.channels_max	= 2,
+	},
+	.ops = &msiof_dai_ops,
+};
+
+static struct snd_pcm_hardware msiof_pcm_hardware = {
+	.info =	SNDRV_PCM_INFO_INTERLEAVED	|
+		SNDRV_PCM_INFO_MMAP		|
+		SNDRV_PCM_INFO_MMAP_VALID,
+	.buffer_bytes_max	= 64 * 1024,
+	.period_bytes_min	= 32,
+	.period_bytes_max	= 8192,
+	.periods_min		= 1,
+	.periods_max		= 32,
+	.fifo_size		= 64,
+};
+
+static int msiof_open(struct snd_soc_component *component,
+		      struct snd_pcm_substream *substream)
+{
+	struct device *dev = component->dev;
+	struct dma_chan *chan;
+	static const char *dma_names[] = {"rx", "tx"};
+	int is_play = msiof_is_play(substream);
+	int ret;
+
+	chan = of_dma_request_slave_channel(dev->of_node, dma_names[is_play]);
+	if (IS_ERR_OR_NULL(chan))
+		return PTR_ERR(chan);
+
+	ret = snd_dmaengine_pcm_open(substream, chan);
+	if (ret < 0)
+		goto open_err_dma;
+
+	snd_soc_set_runtime_hwparams(substream, &msiof_pcm_hardware);
+
+	ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+open_err_dma:
+	if (ret < 0)
+		dma_release_channel(chan);
+
+	return ret;
+}
+
+static int msiof_close(struct snd_soc_component *component,
+		       struct snd_pcm_substream *substream)
+{
+	return snd_dmaengine_pcm_close_release_chan(substream);
+}
+
+static snd_pcm_uframes_t msiof_pointer(struct snd_soc_component *component,
+				       struct snd_pcm_substream *substream)
+{
+	return snd_dmaengine_pcm_pointer(substream);
+}
+
+#define PREALLOC_BUFFER		(32 * 1024)
+#define PREALLOC_BUFFER_MAX	(32 * 1024)
+static int msiof_new(struct snd_soc_component *component,
+		     struct snd_soc_pcm_runtime *rtd)
+{
+	snd_pcm_set_managed_buffer_all(rtd->pcm, SNDRV_DMA_TYPE_DEV,
+				       rtd->card->snd_card->dev,
+				       PREALLOC_BUFFER, PREALLOC_BUFFER_MAX);
+	return 0;
+}
+
+static int msiof_trigger(struct snd_soc_component *component,
+			 struct snd_pcm_substream *substream, int cmd)
+{
+	struct device *dev = component->dev;
+	struct msiof_priv *priv = dev_get_drvdata(dev);
+	unsigned long flags;
+	int ret = -EINVAL;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		priv->substream[substream->stream] = substream;
+		fallthrough;
+	case SNDRV_PCM_TRIGGER_RESUME:
+		ret = msiof_hw_start(component, substream, cmd);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		priv->substream[substream->stream] = NULL;
+		fallthrough;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ret = msiof_hw_stop(component, substream, cmd);
+		break;
+	}
+
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return ret;
+}
+
+static int msiof_hw_params(struct snd_soc_component *component,
+			   struct snd_pcm_substream *substream,
+			   struct snd_pcm_hw_params *params)
+{
+	struct msiof_priv *priv = dev_get_drvdata(component->dev);
+	struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
+	struct dma_slave_config cfg = {};
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&priv->lock, flags);
+
+	ret = snd_hwparams_to_dma_slave_config(substream, params, &cfg);
+	if (ret < 0)
+		goto hw_params_out;
+
+	cfg.dst_addr = priv->phy_addr + SITFDR;
+	cfg.src_addr = priv->phy_addr + SIRFDR;
+
+	ret = dmaengine_slave_config(chan, &cfg);
+hw_params_out:
+	spin_unlock_irqrestore(&priv->lock, flags);
+
+	return ret;
+}
+
+static const struct snd_soc_component_driver msiof_component_driver = {
+	.name		= "msiof",
+	.open		= msiof_open,
+	.close		= msiof_close,
+	.pointer	= msiof_pointer,
+	.pcm_construct	= msiof_new,
+	.trigger	= msiof_trigger,
+	.hw_params	= msiof_hw_params,
+};
+
+static irqreturn_t msiof_interrupt(int irq, void *data)
+{
+	struct msiof_priv *priv = data;
+	struct snd_pcm_substream *substream;
+	u32 sistr;
+
+	spin_lock(&priv->lock);
+
+	sistr = msiof_read(priv, SISTR);
+	msiof_status_clear(priv);
+
+	spin_unlock(&priv->lock);
+
+	/* overflow/underflow error */
+	substream = priv->substream[SNDRV_PCM_STREAM_PLAYBACK];
+	if (substream && (sistr & SISTR_ERR_TX)) {
+		// snd_pcm_stop_xrun(substream);
+		if (sistr & TFSERR)
+			priv->err_syc[SNDRV_PCM_STREAM_PLAYBACK]++;
+		if (sistr & TFOVF)
+			priv->err_ovf[SNDRV_PCM_STREAM_PLAYBACK]++;
+		if (sistr & TFUDF)
+			priv->err_udf[SNDRV_PCM_STREAM_PLAYBACK]++;
+	}
+
+	substream = priv->substream[SNDRV_PCM_STREAM_CAPTURE];
+	if (substream && (sistr & SISTR_ERR_RX)) {
+		// snd_pcm_stop_xrun(substream);
+		if (sistr & RFSERR)
+			priv->err_syc[SNDRV_PCM_STREAM_CAPTURE]++;
+		if (sistr & RFOVF)
+			priv->err_ovf[SNDRV_PCM_STREAM_CAPTURE]++;
+		if (sistr & RFUDF)
+			priv->err_udf[SNDRV_PCM_STREAM_CAPTURE]++;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int msiof_probe(struct platform_device *pdev)
+{
+	struct msiof_priv *priv;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct device_node *port;
+	int irq, ret;
+
+	/* Check MSIOF as Sound mode or SPI mode */
+	port = of_graph_get_next_port(dev->of_node, NULL);
+	if (!port)
+		return -ENODEV;
+	of_node_put(port);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -ENODEV;
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq <= 0)
+		return -ENODEV;
+
+	priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+	if (!priv)
+		return -ENODEV;
+
+	priv->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(priv->base))
+		return PTR_ERR(priv->base);
+
+	ret = devm_request_irq(dev, irq, msiof_interrupt, 0, dev_name(dev), priv);
+	if (ret)
+		return ret;
+
+	priv->dev	= dev;
+	priv->phy_addr	= res->start;
+
+	spin_lock_init(&priv->lock);
+	platform_set_drvdata(pdev, priv);
+
+	devm_pm_runtime_enable(dev);
+
+	ret = devm_snd_soc_register_component(dev, &msiof_component_driver,
+					      &msiof_dai_driver, 1);
+	if (ret)
+		return ret;
+
+	dev_info(dev, "probed\n");
+
+	return ret;
+}
+
+static const struct of_device_id msiof_of_match[] = {
+	{ .compatible = "renesas,rcar-gen4-msiof", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, msiof_of_match);
+
+static struct platform_driver msiof_driver = {
+	.driver 	= {
+		.name	= "msiof-pcm-audio",
+		.of_match_table = msiof_of_match,
+	},
+	.probe		= msiof_probe,
+};
+module_platform_driver(msiof_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Renesas R-Car MSIOF I2S audio driver");
+MODULE_AUTHOR("Kuninori Morimoto <kuninori.morimoto.gx@renesas.com>");
+MODULE_ALIAS("platform:msiof-pcm-audio");