[V5,3/3] ASoC: AMD: add AMD ASoC ACP-I2S driver
diff mbox

Message ID 1440106594-29564-1-git-send-email-alexander.deucher@amd.com
State New
Headers show

Commit Message

Alex Deucher Aug. 20, 2015, 9:36 p.m. UTC
From: Maruthi Srinivas Bayyavarapu <Maruthi.Bayyavarapu@amd.com>

ACP IP block consists of dedicated DMA and I2S blocks. The PCM driver
provides the DMA and CPU DAI components to ALSA core.

Signed-off-by: Maruthi Bayyavarapu <maruthi.bayyavarapu@amd.com>
Reviewed-by: Alex Deucher <alexander.deucher@amd.com>
Reviewed-by: Murali Krishna Vemuri <murali-krishna.vemuri@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
---

v2: squash in Kconfig fix
v3: squash additional commits, convert to mfd, drop rt286 changes
v4: squash in naming fixes
v5: move patch versioning out of commit message

The module licensing follows the model used in the drm drivers.

 sound/soc/Kconfig           |   1 +
 sound/soc/Makefile          |   1 +
 sound/soc/amd/Kconfig       |   5 +
 sound/soc/amd/Makefile      |   3 +
 sound/soc/amd/acp-pcm-dma.c | 676 ++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 686 insertions(+)
 create mode 100644 sound/soc/amd/Kconfig
 create mode 100644 sound/soc/amd/Makefile
 create mode 100644 sound/soc/amd/acp-pcm-dma.c

Comments

Mark Brown Aug. 20, 2015, 11:18 p.m. UTC | #1
On Thu, Aug 20, 2015 at 05:36:34PM -0400, Alex Deucher wrote:
> From: Maruthi Srinivas Bayyavarapu <Maruthi.Bayyavarapu@amd.com>
> 
> ACP IP block consists of dedicated DMA and I2S blocks. The PCM driver
> provides the DMA and CPU DAI components to ALSA core.

This is flagged as patch 3/3 but appears to be sent as a single patch.
Please don't do this, the purpose of numbering patches is to help people
tell which order they are in.  Numbering only makes sense when you send
more than one patch, if you are sending a single patch there is no need
to number.

> +++ b/sound/soc/amd/Kconfig
> @@ -0,0 +1,5 @@
> +config SND_SOC_AMD_ACP
> +	tristate "AMD Audio Coprocessor support"
> +	depends on MFD_CORE

What is the dependency on the MFD core?

> +/*
> + * AMD ALSA SoC PCM Driver
> + *
> + * Copyright 2014-2015 Advanced Micro Devices, Inc.
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the "Software"),
> + * to deal in the Software without restriction, including without limitation
> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> + * and/or sell copies of the Software, and to permit persons to whom the
> + * Software is furnished to do so, subject to the following conditions:
> + *
> + * The above copyright notice and this permission notice shall be included in
> + * all copies or substantial portions of the Software.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
> + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
> + * OTHER DEALINGS IN THE SOFTWARE.

Still not happy with the licensing.

> +static const struct snd_soc_component_driver dw_i2s_component = {
> +	.name = "dw-i2s",
> +};

We already have a driver for the DesignWare I2S controller.  To repeat
the concern I raised in a slightly different bit of the code last time:

| This doesn't appear to be a designware-i2s driver, we already have one
| of those?  

Like I say please stop ignoring review comments.  The hw_params()
certainly seems to bear more than a passing resemblance.

> +static void acp_pcm_period_elapsed(struct device *dev, u16 play_intr,
> +							u16 capture_intr)
> +{
> +	struct snd_pcm_substream *substream;
> +	struct audio_drv_data *irq_data = dev_get_drvdata(dev);
> +
> +	/* Inform ALSA about the period elapsed (one out of two periods) */
> +	if (play_intr)
> +		substream = irq_data->play_stream;
> +	else
> +		substream = irq_data->capture_stream;

So you've replaced the two if statements with an if/else which means
subsstream is now guaranteed to be initialized but perhaps not in the
way the caller intended...  I did find the caller (which got moved into
another patch in this version) and it appears that the two u16s are
actually used to pass boolean flags.  The whole interface here now
seems odd, what is going on here?

> +	if (NULL != pg) {

We still normally write these checks more like (pg != NULL) or even just
(pg).

> +		/* Save for runtime private data */
> +		rtd->pg = pg;
> +		rtd->order = get_order(size);
> +
> +		/* Let ACP know the Allocated memory */
> +		num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
> +
> +		/* Fill the page table entries in ACP SRAM */
> +		rtd->pg = pg;
> +		rtd->size = size;
> +		rtd->num_of_pages = num_of_pages;
> +		rtd->direction = substream->stream;
> +
> +		acp_dev->config_dma(acp_dev, rtd);
> +		status = 0;
> +	} else {
> +		status = -ENOMEM;
> +	}
> +	return status;
> +}
> +
> +static int acp_dma_hw_free(struct snd_pcm_substream *substream)
> +{
> +	return snd_pcm_lib_free_pages(substream);
> +}

hw_free() doesn't seem to undo everything that we did on allocation?

> +		/* Now configure DMA to transfer only first half of System RAM
> +		 * buffer before playback is triggered. This will overwrite
> +		 * zero-ed second half of SRAM buffer */
> +		acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
> +					PLAYBACK_START_DMA_DESCR_CH12,
> +					1, 0);

| Why?  The comments describe what's happening but it's not clear why it's
| happening.

> +static struct snd_soc_dai_driver i2s_dai_driver = {
> +	.playback = {
> +		     .stream_name = "I2S Playback",
> +		     .channels_min = 2,
> +		     .channels_max = 2,

Elsewhere support for 8 channels was declared and handled.

> +		     .rates = SNDRV_PCM_RATE_8000_96000,
> +		     .formats = SNDRV_PCM_FMTBIT_S24_LE |
> +				SNDRV_PCM_FMTBIT_S32_LE,

Elsewhere there was 16 bit support declared and handled.

> +	pm_rts = pm_runtime_status_suspended(dev);
> +	if (pm_rts == true) {

There seem to be a lot of variables in here that have only one
assignment and one user immediately next to them.  I'm not sure it's
adding much.
Alex Deucher Aug. 20, 2015, 11:45 p.m. UTC | #2
On Thu, Aug 20, 2015 at 7:18 PM, Mark Brown <broonie@kernel.org> wrote:
> On Thu, Aug 20, 2015 at 05:36:34PM -0400, Alex Deucher wrote:
>> From: Maruthi Srinivas Bayyavarapu <Maruthi.Bayyavarapu@amd.com>
>>
>> ACP IP block consists of dedicated DMA and I2S blocks. The PCM driver
>> provides the DMA and CPU DAI components to ALSA core.
>
> This is flagged as patch 3/3 but appears to be sent as a single patch.
> Please don't do this, the purpose of numbering patches is to help people
> tell which order they are in.  Numbering only makes sense when you send
> more than one patch, if you are sending a single patch there is no need
> to number.

Sorry, I just saw this reply now.  I just resent the patch with an
improved change log.  You an ignore that one.

Maruthi, can you comment on the points below and respin the patches as
necessary?

Thanks,

Alex

>
>> +++ b/sound/soc/amd/Kconfig
>> @@ -0,0 +1,5 @@
>> +config SND_SOC_AMD_ACP
>> +     tristate "AMD Audio Coprocessor support"
>> +     depends on MFD_CORE
>
> What is the dependency on the MFD core?
>
>> +/*
>> + * AMD ALSA SoC PCM Driver
>> + *
>> + * Copyright 2014-2015 Advanced Micro Devices, Inc.
>> + *
>> + * Permission is hereby granted, free of charge, to any person obtaining a
>> + * copy of this software and associated documentation files (the "Software"),
>> + * to deal in the Software without restriction, including without limitation
>> + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
>> + * and/or sell copies of the Software, and to permit persons to whom the
>> + * Software is furnished to do so, subject to the following conditions:
>> + *
>> + * The above copyright notice and this permission notice shall be included in
>> + * all copies or substantial portions of the Software.
>> + *
>> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
>> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
>> + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
>> + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
>> + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
>> + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
>> + * OTHER DEALINGS IN THE SOFTWARE.
>
> Still not happy with the licensing.
>
>> +static const struct snd_soc_component_driver dw_i2s_component = {
>> +     .name = "dw-i2s",
>> +};
>
> We already have a driver for the DesignWare I2S controller.  To repeat
> the concern I raised in a slightly different bit of the code last time:
>
> | This doesn't appear to be a designware-i2s driver, we already have one
> | of those?
>
> Like I say please stop ignoring review comments.  The hw_params()
> certainly seems to bear more than a passing resemblance.
>
>> +static void acp_pcm_period_elapsed(struct device *dev, u16 play_intr,
>> +                                                     u16 capture_intr)
>> +{
>> +     struct snd_pcm_substream *substream;
>> +     struct audio_drv_data *irq_data = dev_get_drvdata(dev);
>> +
>> +     /* Inform ALSA about the period elapsed (one out of two periods) */
>> +     if (play_intr)
>> +             substream = irq_data->play_stream;
>> +     else
>> +             substream = irq_data->capture_stream;
>
> So you've replaced the two if statements with an if/else which means
> subsstream is now guaranteed to be initialized but perhaps not in the
> way the caller intended...  I did find the caller (which got moved into
> another patch in this version) and it appears that the two u16s are
> actually used to pass boolean flags.  The whole interface here now
> seems odd, what is going on here?
>
>> +     if (NULL != pg) {
>
> We still normally write these checks more like (pg != NULL) or even just
> (pg).
>
>> +             /* Save for runtime private data */
>> +             rtd->pg = pg;
>> +             rtd->order = get_order(size);
>> +
>> +             /* Let ACP know the Allocated memory */
>> +             num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
>> +
>> +             /* Fill the page table entries in ACP SRAM */
>> +             rtd->pg = pg;
>> +             rtd->size = size;
>> +             rtd->num_of_pages = num_of_pages;
>> +             rtd->direction = substream->stream;
>> +
>> +             acp_dev->config_dma(acp_dev, rtd);
>> +             status = 0;
>> +     } else {
>> +             status = -ENOMEM;
>> +     }
>> +     return status;
>> +}
>> +
>> +static int acp_dma_hw_free(struct snd_pcm_substream *substream)
>> +{
>> +     return snd_pcm_lib_free_pages(substream);
>> +}
>
> hw_free() doesn't seem to undo everything that we did on allocation?
>
>> +             /* Now configure DMA to transfer only first half of System RAM
>> +              * buffer before playback is triggered. This will overwrite
>> +              * zero-ed second half of SRAM buffer */
>> +             acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
>> +                                     PLAYBACK_START_DMA_DESCR_CH12,
>> +                                     1, 0);
>
> | Why?  The comments describe what's happening but it's not clear why it's
> | happening.
>
>> +static struct snd_soc_dai_driver i2s_dai_driver = {
>> +     .playback = {
>> +                  .stream_name = "I2S Playback",
>> +                  .channels_min = 2,
>> +                  .channels_max = 2,
>
> Elsewhere support for 8 channels was declared and handled.
>
>> +                  .rates = SNDRV_PCM_RATE_8000_96000,
>> +                  .formats = SNDRV_PCM_FMTBIT_S24_LE |
>> +                             SNDRV_PCM_FMTBIT_S32_LE,
>
> Elsewhere there was 16 bit support declared and handled.
>
>> +     pm_rts = pm_runtime_status_suspended(dev);
>> +     if (pm_rts == true) {
>
> There seem to be a lot of variables in here that have only one
> assignment and one user immediately next to them.  I'm not sure it's
> adding much.
maruthi srinivas Aug. 21, 2015, 11:51 a.m. UTC | #3
On Fri, Aug 21, 2015 at 4:48 AM, Mark Brown <broonie@kernel.org> wrote:
>
> On Thu, Aug 20, 2015 at 05:36:34PM -0400, Alex Deucher wrote:
> > From: Maruthi Srinivas Bayyavarapu <Maruthi.Bayyavarapu@amd.com>
> >
> > ACP IP block consists of dedicated DMA and I2S blocks. The PCM driver
> > provides the DMA and CPU DAI components to ALSA core.
>
> This is flagged as patch 3/3 but appears to be sent as a single patch.
> Please don't do this, the purpose of numbering patches is to help people
> tell which order they are in.  Numbering only makes sense when you send
> more than one patch, if you are sending a single patch there is no need
> to number.

Ok, sorry. I will be more careful next time. Actually, two patches are
sent - one for
DRM and other ASoC. It should have been represented as 2/2.

>
>
> > +++ b/sound/soc/amd/Kconfig
> > @@ -0,0 +1,5 @@
> > +config SND_SOC_AMD_ACP
> > +     tristate "AMD Audio Coprocessor support"
> > +     depends on MFD_CORE
>
> What is the dependency on the MFD core?

None. Sorry, I forgot to remove this.
Actually, AMD DRM driver depends on MFD_CORE to create a platform device
for ASoC. This is already present in DRM patch.

>
>
> > +/*
> > + * AMD ALSA SoC PCM Driver
> > + *
> > + * Copyright 2014-2015 Advanced Micro Devices, Inc.
> > + *
> > + * Permission is hereby granted, free of charge, to any person obtaining a
> > + * copy of this software and associated documentation files (the "Software"),
> > + * to deal in the Software without restriction, including without limitation
> > + * the rights to use, copy, modify, merge, publish, distribute, sublicense,
> > + * and/or sell copies of the Software, and to permit persons to whom the
> > + * Software is furnished to do so, subject to the following conditions:
> > + *
> > + * The above copyright notice and this permission notice shall be included in
> > + * all copies or substantial portions of the Software.
> > + *
> > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> > + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
> > + * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
> > + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
> > + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
> > + * OTHER DEALINGS IN THE SOFTWARE.
>
> Still not happy with the licensing.

 I will contact our internal teams and get back on this.

>
> > +static const struct snd_soc_component_driver dw_i2s_component = {
> > +     .name = "dw-i2s",
> > +};
>
> We already have a driver for the DesignWare I2S controller.  To repeat
> the concern I raised in a slightly different bit of the code last time:
>
> | This doesn't appear to be a designware-i2s driver, we already have one
> | of those?
>

Our IP block includes few AMD IPs along with DesignWare I2S IP.  I have reused
code from Designware I2S controller from
sound/soc/dwc/designware_i2s.c and because
of the way IPs are coupled together, I couldn't use existing
Designware I2S driver as is.
I have given credit to the original author in DRM patch copyright
header, where register I2S read/writes
are made. Do I need to add the same header in ASoC driver too ?

>
> Like I say please stop ignoring review comments.  The hw_params()
> certainly seems to bear more than a passing resemblance.
>

Iam sorry,  this mistake won't be repeated.

>
> > +static void acp_pcm_period_elapsed(struct device *dev, u16 play_intr,
> > +                                                     u16 capture_intr)
> > +{
> > +     struct snd_pcm_substream *substream;
> > +     struct audio_drv_data *irq_data = dev_get_drvdata(dev);
> > +
> > +     /* Inform ALSA about the period elapsed (one out of two periods) */
> > +     if (play_intr)
> > +             substream = irq_data->play_stream;
> > +     else
> > +             substream = irq_data->capture_stream;
>
> So you've replaced the two if statements with an if/else which means
> subsstream is now guaranteed to be initialized but perhaps not in the
> way the caller intended...  I did find the caller (which got moved into
> another patch in this version) and it appears that the two u16s are
> actually used to pass boolean flags.  The whole interface here now
> seems odd, what is going on here?
>

Agreed. I will modify this interface to use a bool, in next revision

>
> > +     if (NULL != pg) {
>
> We still normally write these checks more like (pg != NULL) or even just
> (pg).
>

Ok, will modify accordingly in next revision.

>
> > +             /* Save for runtime private data */
> > +             rtd->pg = pg;
> > +             rtd->order = get_order(size);
> > +
> > +             /* Let ACP know the Allocated memory */
> > +             num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
> > +
> > +             /* Fill the page table entries in ACP SRAM */
> > +             rtd->pg = pg;
> > +             rtd->size = size;
> > +             rtd->num_of_pages = num_of_pages;
> > +             rtd->direction = substream->stream;
> > +
> > +             acp_dev->config_dma(acp_dev, rtd);
> > +             status = 0;
> > +     } else {
> > +             status = -ENOMEM;
> > +     }
> > +     return status;
> > +}
> > +
> > +static int acp_dma_hw_free(struct snd_pcm_substream *substream)
> > +{
> > +     return snd_pcm_lib_free_pages(substream);
> > +}
>
> hw_free() doesn't seem to undo everything that we did on allocation?


Oh! I freed  in dma_close(), that were  allocated in dma_open(). Rest
of them  used devm_*
>
>
> > +             /* Now configure DMA to transfer only first half of System RAM
> > +              * buffer before playback is triggered. This will overwrite
> > +              * zero-ed second half of SRAM buffer */
> > +             acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
> > +                                     PLAYBACK_START_DMA_DESCR_CH12,
> > +                                     1, 0);
>
> | Why?  The comments describe what's happening but it's not clear why it's
> | happening.
>

|------------|----------|
|    A        |       B   |   DRAM
|------------|----------|
           \    /
            \  /
             \/  DMA
             /\
|---------/-|-\--------|
|    C   /   |  \    D  |   SRAM ---DMA ---------> I2S
|-----------|----------|

buffer in System memory(DRAM) is divided into 2 periods - say A,B.
internal memory
inside ACP IP (SRAM) is also divided in similar way - say C,D. In
hw_params()  A and B
are filled with zeros. In prepare() , content of A and B are DMA'ed to
D and C respectively
(as per DMA configuration).  When ALSA core fills A and B with audio content
(start threshold = A+B size), trigger() is called and content of A is
transferred to D.
C still holds zeros by now. The internal buffer (SRAM) which holds C+D
will be DMA'ed to
I2S in cyclic way starting from C.

At the end of C or D's DMA to I2S, irq is generated. In irq handler
DMA is done from B to C
or A to D depending on C or D' DMA completion respectively and
snd_pcm_period_elapsed()
is called.

The reason for doing this is :  When C completes rendering, calls
period_elapsed() and informs
ALSA core there is free space to fill new data to system memory. In
the same irq handler, B is
DMA'ed to C to be ready by the time D completes rendering with
prefetched data (in trigger()).
when D completes rendering, new data fetched by ALSA core can be
DMA'ed  from A to D and
rendering is continued with C. This is done cyclically.

If I don’t do A->D, B->C and instead do A->C, B->D,  there would be
timing problem to have new
data ready to be rendered. The alternate approach would be implement
'copy' callback of
'snd_pcm_ops' in pcm driver. In that callback, I can use
copy_from_user()  to get new data and
then only issue DMA transfers (A->C / B->D). This can solve the timing
issue mentioned, but then
I can't handle MMAP based playback/capture. Existing design can handle
non-mmap and mmap based
transfers, but the only issue(?) would be 1 period size of zeros will
be played initially for any new
playback usecase.

> > +static struct snd_soc_dai_driver i2s_dai_driver = {
> > +     .playback = {
> > +                  .stream_name = "I2S Playback",
> > +                  .channels_min = 2,
> > +                  .channels_max = 2,
>
> Elsewhere support for 8 channels was declared and handled.

The board for which driver is developed, doesn't support more than 2 channels.

>
> > +                  .rates = SNDRV_PCM_RATE_8000_96000,
> > +                  .formats = SNDRV_PCM_FMTBIT_S24_LE |
> > +                             SNDRV_PCM_FMTBIT_S32_LE,
>
> Elsewhere there was 16 bit support declared and handled.

There is a bug in our I2S IP, which wrongly handles 16 bit and lower
resolutions.
So, I removed the support. All the players using the driver, will
handle unsupported
formats with the help of pulseaudio conversions.

>
> > +     pm_rts = pm_runtime_status_suspended(dev);
> > +     if (pm_rts == true) {
>
> There seem to be a lot of variables in here that have only one
> assignment and one user immediately next to them.  I'm not sure it's
> adding much.
>

Ok, I will reuse  variables, where required and remove extra ones in
the next version.

> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel@alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
Mark Brown Aug. 21, 2015, 4:17 p.m. UTC | #4
On Fri, Aug 21, 2015 at 05:21:07PM +0530, maruthi srinivas wrote:
> On Fri, Aug 21, 2015 at 4:48 AM, Mark Brown <broonie@kernel.org> wrote:

> > We already have a driver for the DesignWare I2S controller.  To repeat
> > the concern I raised in a slightly different bit of the code last time:

> > | This doesn't appear to be a designware-i2s driver, we already have one
> > | of those?

> Our IP block includes few AMD IPs along with DesignWare I2S IP.  I have reused
> code from Designware I2S controller from
> sound/soc/dwc/designware_i2s.c and because
> of the way IPs are coupled together, I couldn't use existing
> Designware I2S driver as is.

Could you be more specific about the way in which the IPs are coupled
and the problems this causes please?

> I have given credit to the original author in DRM patch copyright
> header, where register I2S read/writes
> are made. Do I need to add the same header in ASoC driver too ?

What I'm looking for is actual code sharing where we use the same code
for the I2S controller block or a clear and documented understanding of
why it is not possible to share things.

> Oh! I freed  in dma_close(), that were  allocated in dma_open(). Rest
> of them  used devm_*

OK.

> > > +             /* Now configure DMA to transfer only first half of System RAM
> > > +              * buffer before playback is triggered. This will overwrite
> > > +              * zero-ed second half of SRAM buffer */
> > > +             acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
> > > +                                     PLAYBACK_START_DMA_DESCR_CH12,
> > > +                                     1, 0);
> >
> > | Why?  The comments describe what's happening but it's not clear why it's
> > | happening.

> The reason for doing this is :  When C completes rendering, calls
> period_elapsed() and informs
> ALSA core there is free space to fill new data to system memory. In
> the same irq handler, B is
> DMA'ed to C to be ready by the time D completes rendering with
> prefetched data (in trigger()).
> when D completes rendering, new data fetched by ALSA core can be
> DMA'ed  from A to D and
> rendering is continued with C. This is done cyclically.

My point here is that the internal documentation in the driver should be
improved so that someone reading the code can tell why this is being
done.  It doesn't need to be this full explanation but at least enough
for people to be aware of the general issue.

> > > +static struct snd_soc_dai_driver i2s_dai_driver = {
> > > +     .playback = {
> > > +                  .stream_name = "I2S Playback",
> > > +                  .channels_min = 2,
> > > +                  .channels_max = 2,

> > Elsewhere support for 8 channels was declared and handled.

> The board for which driver is developed, doesn't support more than 2 channels.

This is a driver for the IP, not for the board - you may not be able to
test everything but code should try to be as general as it can be.
Alex Deucher Aug. 24, 2015, 8:08 p.m. UTC | #5
On Fri, Aug 21, 2015 at 12:17 PM, Mark Brown <broonie@kernel.org> wrote:
> On Fri, Aug 21, 2015 at 05:21:07PM +0530, maruthi srinivas wrote:
>> On Fri, Aug 21, 2015 at 4:48 AM, Mark Brown <broonie@kernel.org> wrote:
>
>> > We already have a driver for the DesignWare I2S controller.  To repeat
>> > the concern I raised in a slightly different bit of the code last time:
>
>> > | This doesn't appear to be a designware-i2s driver, we already have one
>> > | of those?
>
>> Our IP block includes few AMD IPs along with DesignWare I2S IP.  I have reused
>> code from Designware I2S controller from
>> sound/soc/dwc/designware_i2s.c and because
>> of the way IPs are coupled together, I couldn't use existing
>> Designware I2S driver as is.
>
> Could you be more specific about the way in which the IPs are coupled
> and the problems this causes please?
>
>> I have given credit to the original author in DRM patch copyright
>> header, where register I2S read/writes
>> are made. Do I need to add the same header in ASoC driver too ?
>
> What I'm looking for is actual code sharing where we use the same code
> for the I2S controller block or a clear and documented understanding of
> why it is not possible to share things.
>

Maruthi can clarify further, but it's not possible to use the
designware driver directly because:
1. The i2s registers are within the same MMIO aperture as our other
GPU registers.  Our GPU driver is designed in such a way that the
specific IP modules don't have direct access to the MMIO aperture.
They use functions provided by the core driver to access registers.
Thus the ACP IP module within the GPU driver does not have direct
access to the mmio base pointer in order to pass it on.
2. The designware driver depends on the CLKDEV framework which we
don't currently support.
3. Our hardware does not support S16_LE

Alex


>> Oh! I freed  in dma_close(), that were  allocated in dma_open(). Rest
>> of them  used devm_*
>
> OK.
>
>> > > +             /* Now configure DMA to transfer only first half of System RAM
>> > > +              * buffer before playback is triggered. This will overwrite
>> > > +              * zero-ed second half of SRAM buffer */
>> > > +             acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
>> > > +                                     PLAYBACK_START_DMA_DESCR_CH12,
>> > > +                                     1, 0);
>> >
>> > | Why?  The comments describe what's happening but it's not clear why it's
>> > | happening.
>
>> The reason for doing this is :  When C completes rendering, calls
>> period_elapsed() and informs
>> ALSA core there is free space to fill new data to system memory. In
>> the same irq handler, B is
>> DMA'ed to C to be ready by the time D completes rendering with
>> prefetched data (in trigger()).
>> when D completes rendering, new data fetched by ALSA core can be
>> DMA'ed  from A to D and
>> rendering is continued with C. This is done cyclically.
>
> My point here is that the internal documentation in the driver should be
> improved so that someone reading the code can tell why this is being
> done.  It doesn't need to be this full explanation but at least enough
> for people to be aware of the general issue.
>
>> > > +static struct snd_soc_dai_driver i2s_dai_driver = {
>> > > +     .playback = {
>> > > +                  .stream_name = "I2S Playback",
>> > > +                  .channels_min = 2,
>> > > +                  .channels_max = 2,
>
>> > Elsewhere support for 8 channels was declared and handled.
>
>> The board for which driver is developed, doesn't support more than 2 channels.
>
> This is a driver for the IP, not for the board - you may not be able to
> test everything but code should try to be as general as it can be.
Mark Brown Aug. 25, 2015, 6:06 a.m. UTC | #6
On Mon, Aug 24, 2015 at 04:08:31PM -0400, Alex Deucher wrote:
> On Fri, Aug 21, 2015 at 12:17 PM, Mark Brown <broonie@kernel.org> wrote:

> > What I'm looking for is actual code sharing where we use the same code
> > for the I2S controller block or a clear and documented understanding of
> > why it is not possible to share things.

> Maruthi can clarify further, but it's not possible to use the
> designware driver directly because:
> 1. The i2s registers are within the same MMIO aperture as our other
> GPU registers.  Our GPU driver is designed in such a way that the
> specific IP modules don't have direct access to the MMIO aperture.
> They use functions provided by the core driver to access registers.
> Thus the ACP IP module within the GPU driver does not have direct
> access to the mmio base pointer in order to pass it on.

Please explain this in more detail, shared register ranges are very
common and are the sort of things MFDs are supposed to help with.

> 2. The designware driver depends on the CLKDEV framework which we
> don't currently support.

You need to support the clock API, it's very easy to do so so there is
no excuse for doing something custom here.

> 3. Our hardware does not support S16_LE

If you have modified the designware IP to remove this support (why would
anyone do that?) it's a trivial quirk, if the restriction comes from
some other part of the system like the DMA driver then the constraint
will come from that part of the system.
maruthi srinivas Aug. 25, 2015, 9:56 a.m. UTC | #7
On Tue, Aug 25, 2015 at 11:36 AM, Mark Brown <broonie@kernel.org> wrote:
> On Mon, Aug 24, 2015 at 04:08:31PM -0400, Alex Deucher wrote:
>> On Fri, Aug 21, 2015 at 12:17 PM, Mark Brown <broonie@kernel.org> wrote:
>
>> > What I'm looking for is actual code sharing where we use the same code
>> > for the I2S controller block or a clear and documented understanding of
>> > why it is not possible to share things.
>
>> Maruthi can clarify further, but it's not possible to use the
>> designware driver directly because:
>> 1. The i2s registers are within the same MMIO aperture as our other
>> GPU registers.  Our GPU driver is designed in such a way that the
>> specific IP modules don't have direct access to the MMIO aperture.
>> They use functions provided by the core driver to access registers.
>> Thus the ACP IP module within the GPU driver does not have direct
>> access to the mmio base pointer in order to pass it on.
>
> Please explain this in more detail, shared register ranges are very
> common and are the sort of things MFDs are supposed to help with.
>

In our case, ACP I2S driver need not do a  'devm_ioremap_resource' to
get mmio base.
ACP audio IP (DMA + I2S+ Others) registers can be accessed, using
GPU's MMIO base.
During GPU driver design, it was decided that all the register access
for entire GPU MMIO
aperture (includes ACP and others) to be done in GPU module only.
This is implemented in another patch in this patch series using a
abstraction layer.
A set of functions were defined for audio DMA and I2S functionality
within GPU driver
module and those function pointers were passed as platform data to ALSA PCM
driver to handle both DMA and I2S.

>> 2. The designware driver depends on the CLKDEV framework which we
>> don't currently support.
>
> You need to support the clock API, it's very easy to do so so there is
> no excuse for doing something custom here.
>

Codec acts as master in our case to provide clock to i2s controller and
there wasn't a need to use clock APIs unlike in existing designware i2s driver.
There is no custom implementation.

>> 3. Our hardware does not support S16_LE
>
> If you have modified the designware IP to remove this support (why would
> anyone do that?) it's a trivial quirk, if the restriction comes from
> some other part of the system like the DMA driver then the constraint
> will come from that part of the system.

There is a bug in ACP SoC implementation (which combines internal DMA,
designware I2S
and other blocks) for 16bit and lower resolution. I felt , it would be
better to limit functionality
in I2S DAI capabilities. I will put  this limitation in DMA driver
capabilities, to represent overall
sound card capabilities, if you suggest.
Mark Brown Aug. 25, 2015, 6:32 p.m. UTC | #8
On Tue, Aug 25, 2015 at 03:26:54PM +0530, maruthi srinivas wrote:
> On Tue, Aug 25, 2015 at 11:36 AM, Mark Brown <broonie@kernel.org> wrote:

> > Please explain this in more detail, shared register ranges are very
> > common and are the sort of things MFDs are supposed to help with.

> In our case, ACP I2S driver need not do a  'devm_ioremap_resource' to
> get mmio base.

That sounds like a MFD type problem...

> ACP audio IP (DMA + I2S+ Others) registers can be accessed, using
> GPU's MMIO base.
> During GPU driver design, it was decided that all the register access
> for entire GPU MMIO
> aperture (includes ACP and others) to be done in GPU module only.
> This is implemented in another patch in this patch series using a
> abstraction layer.

That sounds like converting the Designware driver to use regmap and
providing a regmap would enable code sharing (you can provide a regmap
for accessors if you don't use it in the main driver).

> >> 2. The designware driver depends on the CLKDEV framework which we
> >> don't currently support.

> > You need to support the clock API, it's very easy to do so so there is
> > no excuse for doing something custom here.

> Codec acts as master in our case to provide clock to i2s controller and
> there wasn't a need to use clock APIs unlike in existing designware i2s driver.
> There is no custom implementation.

So you just need to add slave mode support to the driver.  Again not a
reason to just copy the code.

> >> 3. Our hardware does not support S16_LE

> > If you have modified the designware IP to remove this support (why would
> > anyone do that?) it's a trivial quirk, if the restriction comes from
> > some other part of the system like the DMA driver then the constraint
> > will come from that part of the system.

> There is a bug in ACP SoC implementation (which combines internal DMA,
> designware I2S
> and other blocks) for 16bit and lower resolution. I felt , it would be
> better to limit functionality
> in I2S DAI capabilities. I will put  this limitation in DMA driver
> capabilities, to represent overall
> sound card capabilities, if you suggest.

A quirk would also do the job.

Patch
diff mbox

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index 2ae9619..1b9ce31 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -32,6 +32,7 @@  config SND_SOC_GENERIC_DMAENGINE_PCM
 
 # All the supported SoCs
 source "sound/soc/adi/Kconfig"
+source "sound/soc/amd/Kconfig"
 source "sound/soc/atmel/Kconfig"
 source "sound/soc/au1x/Kconfig"
 source "sound/soc/bcm/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index e189903..a6cbb99 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -14,6 +14,7 @@  obj-$(CONFIG_SND_SOC)	+= snd-soc-core.o
 obj-$(CONFIG_SND_SOC)	+= codecs/
 obj-$(CONFIG_SND_SOC)	+= generic/
 obj-$(CONFIG_SND_SOC)	+= adi/
+obj-$(CONFIG_SND_SOC)	+= amd/
 obj-$(CONFIG_SND_SOC)	+= atmel/
 obj-$(CONFIG_SND_SOC)	+= au1x/
 obj-$(CONFIG_SND_SOC)	+= bcm/
diff --git a/sound/soc/amd/Kconfig b/sound/soc/amd/Kconfig
new file mode 100644
index 0000000..ce39979
--- /dev/null
+++ b/sound/soc/amd/Kconfig
@@ -0,0 +1,5 @@ 
+config SND_SOC_AMD_ACP
+	tristate "AMD Audio Coprocessor support"
+	depends on MFD_CORE
+	help
+	 This option enables ACP support (DMA,I2S) on AMD platforms.
diff --git a/sound/soc/amd/Makefile b/sound/soc/amd/Makefile
new file mode 100644
index 0000000..1a66ec0
--- /dev/null
+++ b/sound/soc/amd/Makefile
@@ -0,0 +1,3 @@ 
+snd-soc-acp-pcm-objs	:= acp-pcm-dma.o
+
+obj-$(CONFIG_SND_SOC_AMD_ACP) += snd-soc-acp-pcm.o
diff --git a/sound/soc/amd/acp-pcm-dma.c b/sound/soc/amd/acp-pcm-dma.c
new file mode 100644
index 0000000..18cc60c
--- /dev/null
+++ b/sound/soc/amd/acp-pcm-dma.c
@@ -0,0 +1,676 @@ 
+/*
+ * AMD ALSA SoC PCM Driver
+ *
+ * Copyright 2014-2015 Advanced Micro Devices, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER(S) OR AUTHOR(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ *
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/pci.h>
+#include <linux/pm_runtime.h>
+#include <linux/mfd/amd_acp.h>
+
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#define PLAYBACK_MIN_NUM_PERIODS    2
+#define PLAYBACK_MAX_NUM_PERIODS    2
+#define PLAYBACK_MAX_PERIOD_SIZE    16384
+#define PLAYBACK_MIN_PERIOD_SIZE    1024
+#define CAPTURE_MIN_NUM_PERIODS     2
+#define CAPTURE_MAX_NUM_PERIODS     2
+#define CAPTURE_MAX_PERIOD_SIZE     16384
+#define CAPTURE_MIN_PERIOD_SIZE     1024
+
+#define NUM_DSCRS_PER_CHANNEL 2
+
+#define MAX_BUFFER (PLAYBACK_MAX_PERIOD_SIZE * PLAYBACK_MAX_NUM_PERIODS)
+#define MIN_BUFFER MAX_BUFFER
+
+#define TWO_CHANNEL_SUPPORT     2	/* up to 2.0 */
+#define FOUR_CHANNEL_SUPPORT    4	/* up to 3.1 */
+#define SIX_CHANNEL_SUPPORT     6	/* up to 5.1 */
+#define EIGHT_CHANNEL_SUPPORT   8	/* up to 7.1 */
+
+
+static const struct snd_pcm_hardware acp_pcm_hardware_playback = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+	/* formats,rates,channels  based on i2s doc. */
+	.formats = SNDRV_PCM_FMTBIT_S16_LE |
+		SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+	.channels_min = 1,
+	.channels_max = 8,
+	.rates = SNDRV_PCM_RATE_8000_96000,
+	.rate_min = 8000,
+	.rate_max = 96000,
+	.buffer_bytes_max = PLAYBACK_MAX_NUM_PERIODS * PLAYBACK_MAX_PERIOD_SIZE,
+	.period_bytes_min = PLAYBACK_MIN_PERIOD_SIZE,
+	.period_bytes_max = PLAYBACK_MAX_PERIOD_SIZE,
+	.periods_min = PLAYBACK_MIN_NUM_PERIODS,
+	.periods_max = PLAYBACK_MAX_NUM_PERIODS,
+};
+
+static const struct snd_pcm_hardware acp_pcm_hardware_capture = {
+	.info = SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER | SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_MMAP_VALID | SNDRV_PCM_INFO_BATCH |
+	    SNDRV_PCM_INFO_PAUSE | SNDRV_PCM_INFO_RESUME,
+	/* formats,rates,channels  based on i2s doc. */
+	.formats = SNDRV_PCM_FMTBIT_S16_LE |
+		SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+	.channels_min = 1,
+	.channels_max = 2,
+	.rates = SNDRV_PCM_RATE_8000_48000,
+	.rate_min = 8000,
+	.rate_max = 48000,
+	.buffer_bytes_max = CAPTURE_MAX_NUM_PERIODS * CAPTURE_MAX_PERIOD_SIZE,
+	.period_bytes_min = CAPTURE_MIN_PERIOD_SIZE,
+	.period_bytes_max = CAPTURE_MAX_PERIOD_SIZE,
+	.periods_min = CAPTURE_MIN_NUM_PERIODS,
+	.periods_max = CAPTURE_MAX_NUM_PERIODS,
+};
+
+struct audio_drv_data {
+	struct snd_pcm_substream *play_stream;
+	struct snd_pcm_substream *capture_stream;
+	struct amd_acp_device *acp_dev;
+	struct acp_irq_prv *iprv;
+};
+
+static const struct snd_soc_component_driver dw_i2s_component = {
+	.name = "dw-i2s",
+};
+
+static void acp_pcm_period_elapsed(struct device *dev, u16 play_intr,
+							u16 capture_intr)
+{
+	struct snd_pcm_substream *substream;
+	struct audio_drv_data *irq_data = dev_get_drvdata(dev);
+
+	/* Inform ALSA about the period elapsed (one out of two periods) */
+	if (play_intr)
+		substream = irq_data->play_stream;
+	else
+		substream = irq_data->capture_stream;
+
+	if (substream->runtime && snd_pcm_running(substream))
+		snd_pcm_period_elapsed(substream);
+}
+
+static int acp_dma_open(struct snd_pcm_substream *substream)
+{
+	int ret = 0;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *prtd = substream->private_data;
+	struct audio_drv_data *intr_data = dev_get_drvdata(prtd->platform->dev);
+
+	struct audio_substream_data *adata =
+		kzalloc(sizeof(struct audio_substream_data), GFP_KERNEL);
+	if (adata == NULL)
+		return -ENOMEM;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		runtime->hw = acp_pcm_hardware_playback;
+	else
+		runtime->hw = acp_pcm_hardware_capture;
+
+	ret = snd_pcm_hw_constraint_integer(runtime,
+					    SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		dev_err(prtd->platform->dev, "set integer constraint failed\n");
+		return ret;
+	}
+
+	adata->acp_dev = intr_data->acp_dev;
+	runtime->private_data = adata;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+		intr_data->play_stream = substream;
+	else
+		intr_data->capture_stream = substream;
+
+	return 0;
+}
+
+static int acp_dma_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params)
+{
+	int status;
+	uint64_t size;
+	struct snd_dma_buffer *dma_buffer;
+	struct page *pg;
+	u16 num_of_pages;
+	struct snd_pcm_runtime *runtime;
+	struct audio_substream_data *rtd;
+	struct amd_acp_device *acp_dev;
+
+	dma_buffer = &substream->dma_buffer;
+
+	runtime = substream->runtime;
+	rtd = runtime->private_data;
+
+	if (WARN_ON(!rtd))
+		return -EINVAL;
+	acp_dev = rtd->acp_dev;
+
+	size = params_buffer_bytes(params);
+	status = snd_pcm_lib_malloc_pages(substream, size);
+	if (status < 0)
+		return status;
+
+	memset(substream->runtime->dma_area, 0, params_buffer_bytes(params));
+	pg = virt_to_page(substream->dma_buffer.area);
+
+	if (NULL != pg) {
+		/* Save for runtime private data */
+		rtd->pg = pg;
+		rtd->order = get_order(size);
+
+		/* Let ACP know the Allocated memory */
+		num_of_pages = PAGE_ALIGN(size) >> PAGE_SHIFT;
+
+		/* Fill the page table entries in ACP SRAM */
+		rtd->pg = pg;
+		rtd->size = size;
+		rtd->num_of_pages = num_of_pages;
+		rtd->direction = substream->stream;
+
+		acp_dev->config_dma(acp_dev, rtd);
+		status = 0;
+	} else {
+		status = -ENOMEM;
+	}
+	return status;
+}
+
+static int acp_dma_hw_free(struct snd_pcm_substream *substream)
+{
+	return snd_pcm_lib_free_pages(substream);
+}
+
+static snd_pcm_uframes_t acp_dma_pointer(struct snd_pcm_substream *substream)
+{
+	u32 pos = 0;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_substream_data *rtd = runtime->private_data;
+	struct amd_acp_device *acp_dev = rtd->acp_dev;
+
+	pos = acp_dev->update_dma_pointer(acp_dev, substream->stream,
+				frames_to_bytes(runtime, runtime->period_size));
+	return bytes_to_frames(runtime, pos);
+
+}
+
+static int acp_dma_mmap(struct snd_pcm_substream *substream,
+			struct vm_area_struct *vma)
+{
+	return snd_pcm_lib_default_mmap(substream, vma);
+}
+
+static int acp_dma_prepare(struct snd_pcm_substream *substream)
+{
+	int ret;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_substream_data *rtd = runtime->private_data;
+	struct amd_acp_device *acp_dev = rtd->acp_dev;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+
+		acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
+					PLAYBACK_START_DMA_DESCR_CH12,
+					NUM_DSCRS_PER_CHANNEL, 0);
+		acp_dev->config_dma_channel(acp_dev, ACP_TO_I2S_DMA_CH_NUM,
+					PLAYBACK_START_DMA_DESCR_CH13,
+					NUM_DSCRS_PER_CHANNEL, 0);
+		/* Fill ACP SRAM (2 periods) with zeros from System RAM
+		 * which is zero-ed in hw_params */
+		ret = acp_dev->dma_start(rtd->acp_dev,
+						SYSRAM_TO_ACP_CH_NUM, false);
+		if (ret < 0)
+			ret = -EFAULT;
+
+		/* Now configure DMA to transfer only first half of System RAM
+		 * buffer before playback is triggered. This will overwrite
+		 * zero-ed second half of SRAM buffer */
+		acp_dev->config_dma_channel(acp_dev, SYSRAM_TO_ACP_CH_NUM,
+					PLAYBACK_START_DMA_DESCR_CH12,
+					1, 0);
+	} else {
+		acp_dev->config_dma_channel(acp_dev, ACP_TO_SYSRAM_CH_NUM,
+					CAPTURE_START_DMA_DESCR_CH14,
+					NUM_DSCRS_PER_CHANNEL, 0);
+		acp_dev->config_dma_channel(acp_dev, I2S_TO_ACP_DMA_CH_NUM,
+					CAPTURE_START_DMA_DESCR_CH15,
+					NUM_DSCRS_PER_CHANNEL, 0);
+	}
+	return 0;
+}
+
+static int acp_dma_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_substream_data *rtd = runtime->private_data;
+	struct amd_acp_device *acp_dev = rtd->acp_dev;
+	int ret;
+
+	if (!rtd)
+		return -EINVAL;
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			ret = acp_dev->dma_start(rtd->acp_dev,
+						SYSRAM_TO_ACP_CH_NUM, false);
+			if (ret < 0)
+				return -EFAULT;
+			acp_dev->prebuffer_audio(rtd->acp_dev);
+
+			ret = acp_dev->dma_start(acp_dev,
+					    ACP_TO_I2S_DMA_CH_NUM, true);
+		} else {
+			ret = acp_dev->dma_start(acp_dev,
+					    I2S_TO_ACP_DMA_CH_NUM, true);
+		}
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+			ret = acp_dev->dma_stop(acp_dev, SYSRAM_TO_ACP_CH_NUM);
+			if (0 == ret)
+				ret = acp_dev->dma_stop(acp_dev,
+						   ACP_TO_I2S_DMA_CH_NUM);
+		} else {
+			ret = acp_dev->dma_stop(acp_dev, I2S_TO_ACP_DMA_CH_NUM);
+			if (0 == ret)
+				ret = acp_dev->dma_stop(acp_dev,
+						ACP_TO_SYSRAM_CH_NUM);
+		}
+		break;
+	default:
+		ret = -EINVAL;
+
+	}
+	return ret;
+}
+
+static int acp_dma_new(struct snd_soc_pcm_runtime *rtd)
+{
+	int ret;
+	struct snd_pcm *pcm;
+
+	pcm = rtd->pcm;
+	ret = snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_DEV,
+					NULL, MIN_BUFFER, MAX_BUFFER);
+	return ret;
+}
+
+static int acp_dma_close(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_substream_data *rtd = runtime->private_data;
+	struct snd_soc_pcm_runtime *prtd = substream->private_data;
+
+	kfree(rtd);
+
+	pm_runtime_mark_last_busy(prtd->platform->dev);
+	return 0;
+}
+
+static int acp_dai_i2s_hwparams(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *prtd = substream->private_data;
+	struct audio_substream_data *rtd = runtime->private_data;
+	struct amd_acp_device *acp_dev = rtd->acp_dev;
+	struct device *dev = prtd->platform->dev;
+	u32 chan_nr;
+
+	switch (params_format(params)) {
+	case SNDRV_PCM_FORMAT_S16_LE:
+		rtd->xfer_resolution = 0x02;
+		break;
+
+	case SNDRV_PCM_FORMAT_S24_LE:
+		rtd->xfer_resolution = 0x04;
+		break;
+
+	case SNDRV_PCM_FORMAT_S32_LE:
+		rtd->xfer_resolution = 0x05;
+		break;
+
+	default:
+		dev_err(dev, "unsuppted PCM fmt : %d\n", params_format(params));
+		return -EINVAL;
+	}
+
+	chan_nr = params_channels(params);
+
+	switch (chan_nr) {
+	case EIGHT_CHANNEL_SUPPORT:
+		rtd->ch_reg = 3;
+		break;
+	case SIX_CHANNEL_SUPPORT:
+		rtd->ch_reg = 2;
+		break;
+	case FOUR_CHANNEL_SUPPORT:
+		rtd->ch_reg = 1;
+		break;
+	case TWO_CHANNEL_SUPPORT:
+		rtd->ch_reg = 0;
+		break;
+	default:
+		dev_err(dev, "channel not supported : %d\n", chan_nr);
+		return -EINVAL;
+	}
+
+	rtd->direction = substream->stream;
+
+	acp_dev->config_i2s(acp_dev, rtd);
+
+	return 0;
+}
+
+static int acp_dai_i2s_trigger(struct snd_pcm_substream *substream,
+			       int cmd, struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_substream_data *rtd = runtime->private_data;
+	struct amd_acp_device *acp_dev = rtd->acp_dev;
+	int ret;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		acp_dev->i2s_start(acp_dev, substream->stream);
+		ret = 0;
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		acp_dev->i2s_stop(acp_dev, substream->stream);
+		ret = 0;
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+static int acp_dai_i2s_prepare(struct snd_pcm_substream *substream,
+			       struct snd_soc_dai *dai)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct audio_substream_data *rtd = runtime->private_data;
+	struct amd_acp_device *acp_dev = rtd->acp_dev;
+
+	acp_dev->i2s_reset(acp_dev, substream->stream);
+	return 0;
+}
+
+static struct snd_pcm_ops acp_dma_ops = {
+	.open = acp_dma_open,
+	.close = acp_dma_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = acp_dma_hw_params,
+	.hw_free = acp_dma_hw_free,
+	.trigger = acp_dma_trigger,
+	.pointer = acp_dma_pointer,
+	.mmap = acp_dma_mmap,
+	.prepare = acp_dma_prepare,
+};
+
+static struct snd_soc_dai_ops acp_dai_i2s_ops = {
+	.prepare = acp_dai_i2s_prepare,
+	.hw_params = acp_dai_i2s_hwparams,
+	.trigger = acp_dai_i2s_trigger,
+};
+
+static struct snd_soc_dai_driver i2s_dai_driver = {
+	.playback = {
+		     .stream_name = "I2S Playback",
+		     .channels_min = 2,
+		     .channels_max = 2,
+		     .rates = SNDRV_PCM_RATE_8000_96000,
+		     .formats = SNDRV_PCM_FMTBIT_S24_LE |
+				SNDRV_PCM_FMTBIT_S32_LE,
+
+		     .rate_min = 8000,
+		     .rate_max = 96000,
+		     },
+	.capture = {
+		    .stream_name = "I2S Capture",
+		    .channels_min = 2,
+		    .channels_max = 2,
+		    .rates = SNDRV_PCM_RATE_8000_48000,
+		    .formats = SNDRV_PCM_FMTBIT_S24_LE |
+				SNDRV_PCM_FMTBIT_S32_LE,
+		    .rate_min = 8000,
+		    .rate_max = 48000,
+		    },
+	.ops = &acp_dai_i2s_ops,
+};
+
+static struct snd_soc_platform_driver acp_asoc_platform = {
+	.ops = &acp_dma_ops,
+	.pcm_new = acp_dma_new,
+};
+
+static int acp_alsa_register(struct device *dev, struct amd_acp_device *acp_dev,
+				struct platform_device *pdev)
+{
+	int status;
+
+	status = snd_soc_register_platform(dev, &acp_asoc_platform);
+	if (0 != status) {
+		dev_err(dev, "Unable to register ALSA platform device\n");
+		goto exit_platform;
+	} else {
+		status = snd_soc_register_component(dev,
+					&dw_i2s_component,
+					&i2s_dai_driver, 1);
+
+		if (0 != status) {
+			dev_err(dev, "Unable to register i2s dai\n");
+			goto exit_dai;
+		} else {
+			dev_info(dev, "ACP device registered with ALSA\n");
+			return status;
+		}
+	}
+
+exit_dai:
+	snd_soc_unregister_platform(dev);
+exit_platform:
+	acp_dev->fini(acp_dev);
+	return status;
+}
+
+static int acp_audio_probe(struct platform_device *pdev)
+{
+	int status;
+	struct audio_drv_data *audio_drv_data;
+	struct amd_acp_device *acp_dev = dev_get_platdata(&pdev->dev);
+
+	audio_drv_data = devm_kzalloc(&pdev->dev, sizeof(struct audio_drv_data),
+					GFP_KERNEL);
+	if (audio_drv_data == NULL)
+		return -ENOMEM;
+
+	audio_drv_data->iprv = devm_kzalloc(&pdev->dev,
+						sizeof(struct acp_irq_prv),
+						GFP_KERNEL);
+	if (audio_drv_data->iprv == NULL)
+		return -ENOMEM;
+
+	/* The following members gets populated in device 'open'
+	 * function. Till then interrupts are disabled in 'acp_hw_init'
+	 * and device doesn't generate any interrupts.
+	 */
+
+	audio_drv_data->play_stream = NULL;
+	audio_drv_data->capture_stream = NULL;
+	audio_drv_data->acp_dev = acp_dev;
+
+	audio_drv_data->iprv->dev = &pdev->dev;
+	audio_drv_data->iprv->acp_dev = acp_dev;
+	audio_drv_data->iprv->set_elapsed = acp_pcm_period_elapsed;
+
+	dev_set_drvdata(&pdev->dev, audio_drv_data);
+
+	/* Initialize the ACP */
+	status = acp_dev->init(acp_dev, audio_drv_data->iprv);
+
+	if (0 == status)
+		status = acp_alsa_register(&pdev->dev, acp_dev, pdev);
+	else
+		dev_err(&pdev->dev, "ACP initialization Failed\n");
+
+	pm_runtime_set_autosuspend_delay(&pdev->dev, 10000);
+	pm_runtime_use_autosuspend(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	return status;
+}
+
+static int acp_audio_remove(struct platform_device *pdev)
+{
+	struct amd_acp_device *acp_dev = dev_get_platdata(&pdev->dev);
+
+	snd_soc_unregister_component(&pdev->dev);
+	snd_soc_unregister_platform(&pdev->dev);
+
+	acp_dev->fini(acp_dev);
+	pm_runtime_disable(&pdev->dev);
+
+	return 0;
+}
+
+static int acp_pcm_suspend(struct device *dev)
+{
+	bool pm_rts;
+	struct audio_drv_data *adata = dev_get_drvdata(dev);
+
+	pm_rts = pm_runtime_status_suspended(dev);
+	if (pm_rts == false)
+		adata->acp_dev->fini(adata->acp_dev);
+
+	return 0;
+}
+
+static int acp_pcm_resume(struct device *dev)
+{
+	bool pm_rts;
+	struct snd_pcm_substream *pstream, *cstream;
+	struct snd_pcm_runtime *prtd, *crtd;
+	struct audio_substream_data *rtd;
+	struct audio_drv_data *adata = dev_get_drvdata(dev);
+
+	pm_rts = pm_runtime_status_suspended(dev);
+	if (pm_rts == true) {
+		/* Resumed from system wide suspend and there is
+		 * no pending audio activity to resume. */
+		pm_runtime_disable(dev);
+		pm_runtime_set_active(dev);
+		pm_runtime_enable(dev);
+
+		goto out;
+	}
+
+	pstream = adata->play_stream;
+	prtd = pstream ? pstream->runtime : NULL;
+	if (prtd != NULL) {
+		/* Resume playback stream from a suspended state */
+		rtd = prtd->private_data;
+
+		adata->acp_dev->config_dma(adata->acp_dev, rtd);
+		adata->acp_dev->config_i2s(adata->acp_dev, rtd);
+	}
+
+	cstream = adata->capture_stream;
+	crtd =  cstream ? cstream->runtime : NULL;
+	if (crtd != NULL) {
+		/* Resume capture stream from a suspended state */
+		rtd = crtd->private_data;
+
+		adata->acp_dev->config_dma(adata->acp_dev, rtd);
+		adata->acp_dev->config_i2s(adata->acp_dev, rtd);
+	}
+out:
+	return 0;
+}
+
+static int acp_pcm_runtime_suspend(struct device *dev)
+{
+	struct audio_drv_data *adata = dev_get_drvdata(dev);
+
+	adata->acp_dev->acp_suspend(adata->acp_dev);
+	return 0;
+}
+
+static int acp_pcm_runtime_resume(struct device *dev)
+{
+	struct audio_drv_data *adata = dev_get_drvdata(dev);
+
+	adata->acp_dev->acp_resume(adata->acp_dev);
+	return 0;
+}
+
+static const struct dev_pm_ops acp_pm_ops = {
+	.suspend = acp_pcm_suspend,
+	.resume = acp_pcm_resume,
+	.runtime_suspend = acp_pcm_runtime_suspend,
+	.runtime_resume = acp_pcm_runtime_resume,
+};
+
+static struct platform_driver acp_dma_driver = {
+	.probe = acp_audio_probe,
+	.remove = acp_audio_remove,
+	.driver = {
+		.name = "acp-i2s-audio",
+		.pm = &acp_pm_ops,
+	},
+};
+
+module_platform_driver(acp_dma_driver);
+
+MODULE_AUTHOR("Maruthi.Bayyavarapu@amd.com");
+MODULE_DESCRIPTION("AMD ACP PCM Driver");
+MODULE_LICENSE("GPL and additional rights");
+MODULE_ALIAS("platform:acp-i2s-audio");