[alsa-devel] [PATCH 8/8] ASoC: AMD: add AMD ASoC ACP-I2S driver
maruthi srinivas
maruthi.srinivas.b at gmail.com
Fri Oct 23 20:50:09 CEST 2015
On Thu, Oct 22, 2015 at 9:44 PM, Mark Brown <broonie at kernel.org> wrote:
> On Thu, Oct 08, 2015 at 12:12:40PM -0400, Alex Deucher wrote:
>
>> ACP IP block consists of dedicated DMA and I2S blocks. The PCM driver
>> provides the platform DMA component to ALSA core.
>
> Overall my main comment on a lot of this code is that it feels like we
> have created a lot of infrastructure that parallels standard Linux
> subsystems and interfaces without something that clearly shows why we're
> doing that. There may be good reasons but they've not been articulated
> and it's making the code a lot more complex to follow and review. We
> end up with multiple layers of abstraction and indirection that aren't
> explained.
>
> This patch is also rather large and appears to contain multiple
> components which could be split, there's at least the DMA driver and
> this abstraction layer than the DMA driver builds on.
>
>> +/* ACP DMA irq handler routine for playback, capture usecases */
>> +int dma_irq_handler(struct device *dev)
>> +{
>> + u16 dscr_idx;
>> + u32 intr_flag;
>
> This says it's an interrupt handler but it's using some custom,
> non-genirq interface?
>
Irq handling is based on parent device's (part of other subsystem)
provided interfaces. I will coordinate with others for this.
Do you mean, using virtual irq assignment for MFD devices
(ACP is a MFD device) and registering irq handler for it ?
>> + /* 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;
>
> We never reference num_of_pages other than to assign it into the page
> table entry?
>
Sorry, I didn't understand your comment.
I used 'num_of_pages' to configure ACP audio device for accessing system
memory. The implementation is in 'acp_pte_config' function in the patch.
>> +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);
>
> Why the _mark_last_busy() here?
I want to power off ACP audio IP, when the audio usecase is not active for
sometime (run time PM). I felt, 'close' is the correct place to mark this.
>
>> + /* 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->iprv->dev = &pdev->dev;
>> + audio_drv_data->iprv->acp_mmio = audio_drv_data->acp_mmio;
>> + audio_drv_data->iprv->enable_intr = acp_enable_external_interrupts;
>> + audio_drv_data->iprv->irq_handler = dma_irq_handler;
>
> I do not that we never seem to reset any of these in teardown paths and
> I am slightly worried about races with interrupt handling in teardown,
>
I will recheck this.
>> +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)
>> + acp_suspend(adata->acp_mmio);
>> +
>> + return 0;
>> +}
>
> This appears to merely call into the parent/core device (at least it
> looks like this is the intention, there's a bunch of infrastructure fo
> the core device which appeaars to replicate standard infrastructure).
> Isn't whatever this eventually ends up doing handled by the parent
> device in the normal PM callbacks.
>
ACP device (child) can power off itself, when it receives suspend
request. So, the intention is to call 'acp_suspend' (defined in same patch)
from the 'suspend' callback of ACP device.
> This parallel infrastructure seems like it needs some motivation,
> especially given that when I look at the implementation of functions
> like amd_acp_suspend() and amd_acp_resume() in the preceeding patch they
> are empty and therefore do nothing (they're also not exported so I
> expect we get build errors if this is a module and the core isn't).
> The easiest thing is probably to remove the code until there is an
> immplementation and then review at that time.
>
There were two different functions with same name in two drivers
interacting here.That might have introduced some confusion.
Sorry, I will modify that.
Also, amd_acp_*() can be removed later, as they are not expected to add any
functonality in future. ACP device can be suspended/resumed using acp_*_tile().
>> +static int acp_pcm_resume(struct device *dev)
>> +{
>> + bool pm_rts;
>> + struct snd_pcm_substream *stream;
>> + struct snd_pcm_runtime *rtd;
>> + struct audio_substream_data *sdata;
>> + 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);
>
> The above looks very strange - why are we bouncing runtime PM like this?
Sorry, I didn't understand your comment. I felt, steps mentioned in
kernel documentation :
http://lxr.free-electrons.com/source/Documentation/power/runtime_pm.txt#L634
is applicable in this scenario. I maybe wrong, but felt that is applicable.
>
>> +/* Initialize the dma descriptors location in SRAM and page size */
>> +static void acp_dma_descr_init(void __iomem *acp_mmio)
>> +{
>> + u32 sram_pte_offset = 0;
>> +
>> + /* SRAM starts at 0x04000000. From that offset one page (4KB) left for
>> + * filling DMA descriptors.sram_pte_offset = 0x04001000 , used for
>
> This is a device relative address rather than an absolute address? A
> lot of these numbers seem kind of large...
That is SRAM block's offset address. Sorry. I didn't understand the
expected change, here.
>
>> +u16 get_dscr_idx(void __iomem *acp_mmio, int direction)
>> +{
>> + u16 dscr_idx;
>> +
>> + if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
>> + dscr_idx = acp_reg_read(acp_mmio, mmACP_DMA_CUR_DSCR_13);
>> + dscr_idx = (dscr_idx == PLAYBACK_START_DMA_DESCR_CH13) ?
>> + PLAYBACK_START_DMA_DESCR_CH12 :
>> + PLAYBACK_END_DMA_DESCR_CH12;
>
> Please write normal if statements rather than using the ternery
> operator.
>
Ok.
>> +/* Check whether ACP DMA interrupt (IOC) is generated or not */
>> +u32 acp_get_intr_flag(void __iomem *acp_mmio)
>> +{
>> + u32 ext_intr_status;
>> + u32 intr_gen;
>> +
>> + ext_intr_status = acp_reg_read(acp_mmio, mmACP_EXTERNAL_INTR_STAT);
>> + intr_gen = (((ext_intr_status &
>> + ACP_EXTERNAL_INTR_STAT__DMAIOCStat_MASK) >>
>> + ACP_EXTERNAL_INTR_STAT__DMAIOCStat__SHIFT));
>> +
>> + return intr_gen;
>> +}
>
> Looking at a lot of the interrupt code I can't help but think there's a
> genirq interrupt controller lurking in here somewhere.
>
I will coordinate with other subsystem driver author, that this driver
is dependent on.
>> + /*Invalidating the DAGB cache */
>> + acp_reg_write(ENABLE, acp_mmio, mmACP_DAGB_ATU_CTRL);
>
> /* spaces around comments please */
>
>> + if ((ch_num == ACP_TO_I2S_DMA_CH_NUM) ||
>> + (ch_num == ACP_TO_SYSRAM_CH_NUM) ||
>> + (ch_num == I2S_TO_ACP_DMA_CH_NUM))
>> + dma_ctrl |= ACP_DMA_CNTL_0__DMAChIOCEn_MASK;
>> + else
>> + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChIOCEn_MASK;
>
> switch statement please.
Ok.
>
>> + u32 delay_time = ACP_DMA_RESET_TIME;
>
>> + /* check the channel status bit for some time and return the status */
>> + while (0 < delay_time) {
>> + dma_ch_sts = acp_reg_read(acp_mmio, mmACP_DMA_CH_STS);
>> + if (!(dma_ch_sts & BIT(ch_num))) {
>> + /* clear the reset flag after successfully stopping
>> + the dma transfer and break from the loop */
>> + dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRst_MASK;
>> +
>> + acp_reg_write(dma_ctrl, acp_mmio, mmACP_DMA_CNTL_0
>> + + ch_num);
>> + break;
>> + }
>> + delay_time--;
>> + }
>> +}
>
> This isn't really a time, it's a number of spins (the amount of time
> involved presumably depending on clock speed). If this were a time I'd
> expect to see a delay or sleep in here.
>
> We're also falling off the end of the loop silently if the hardware
> fails to respond, if it's worth waiting for the hardware to do it's
> thing I'd expect it's also worth displaying an error if that happens.
> This is a common pattern in much of the rest of the driver.
>
Ok, will modify.
>> +/* power off a tile/block within ACP */
>> +static void acp_suspend_tile(void __iomem *acp_mmio, int tile)
>> +{
>> + u32 val = 0;
>> + u32 timeout = 0;
>> +
>> + if ((tile < ACP_TILE_P1) || (tile > ACP_TILE_DSP2))
>> + return;
>> +
>> + val = acp_reg_read(acp_mmio, mmACP_PGFSM_READ_REG_0 + tile);
>> + val &= ACP_TILE_ON_MASK;
>
> This is definitely looking like a SoC that could benefit from the
> standard kernel power management infrastructure and/or DAPM. There's a
> lot of code here that looks like it's following very common SoC design
> patterns and could benefit from using infrastructure more.
>
Sorry, I didn't understand. Could you help in adding more pointers on this.
The device can get powered-off, with this helper function, which can
be called from device 'suspend' callback.
>> +static void acp_resume_tile(void __iomem *acp_mmio, int tile)
>> +{
>> + u32 val = 0;
>> + u32 timeout = 0;
>> +
>> + if ((tile < ACP_TILE_P1) || (tile > ACP_TILE_DSP2))
>> + return;
>
> Not worth printing an error if the user passed in something invalid?
Ok.
>
>> +/* Shutdown unused SRAM memory banks in ACP IP */
>> +static void acp_turnoff_sram_banks(void __iomem *acp_mmio)
>> +{
>> + /* Bank 0 : used for DMA descriptors
>> + * Bank 1 to 4 : used for playback
>> + * Bank 5 to 8 : used for capture
>> + * Each bank is 8kB and max size allocated for playback/ capture is
>> + * 16kB(max period size) * 2(max periods) reserved for playback/capture
>> + * in ALSA driver
>> + * Turn off all SRAM banks except above banks during playback/capture
>> + */
>> + u32 val, bank;
>
> I didn't see any runtime management of the other SRAM bank power, seems
> like that'd be a good idea?
SRAM banks are part of ACP IP. With ACP's runtime PM handling, all blocks
within ACP IP can be powered-off and on.
>
>> + /* initiailizing Garlic Control DAGB register */
>> + acp_reg_write(ONION_CNTL_DEFAULT, acp_mmio, mmACP_AXI2DAGB_ONION_CNTL);
>> +
>> + /* initiailizing Onion Control DAGB registers */
>> + acp_reg_write(GARLIC_CNTL_DEFAULT, acp_mmio,
>> + mmACP_AXI2DAGB_GARLIC_CNTL);
>
> The comments don't match the code...
Oops, I will correct it.
>
>> +/* Update DMA postion in audio ring buffer at period level granularity.
>> + * This will be used by ALSA PCM driver
>> + */
>> +u32 acp_update_dma_pointer(void __iomem *acp_mmio, int direction,
>> + u32 period_size)
>> +{
>> + u32 pos;
>> + u16 dscr;
>> + u32 mul;
>> + u32 dma_config;
>> +
>> + pos = 0;
>> +
>> + if (direction == SNDRV_PCM_STREAM_PLAYBACK) {
>> + dscr = acp_reg_read(acp_mmio, mmACP_DMA_CUR_DSCR_13);
>> +
>> + mul = (dscr == PLAYBACK_START_DMA_DESCR_CH13) ? 0 : 1;
>> + pos = (mul * period_size);
>
> Don't limit the accuracy to period level if the harwdare can do better,
> report the current position as accurately as possible please. This is
> also feeling like we've got an unneeded abstraction here - why was this
> not just directly the pointer operation?
>
The current version of hardware has the limitation of accuracy reporting.
I will remove abstraction, if suggested.
>> +/* Wait for initial buffering to complete in HOST to SRAM DMA channel
>> + * for plaback usecase
>> + */
>> +void prebuffer_audio(void __iomem *acp_mmio)
>> +{
>> + u32 dma_ch_sts;
>> + u32 channel_mask = BIT(SYSRAM_TO_ACP_CH_NUM);
>> +
>> + do {
>> + /* Read the channel status to poll dma transfer completion
>> + * (System RAM to SRAM)
>> + * In this case, it will be runtime->start_threshold
>> + * (2 ALSA periods) of transfer. Rendering starts after this
>> + * threshold is met.
>> + */
>> + dma_ch_sts = acp_reg_read(acp_mmio, mmACP_DMA_CH_STS);
>> + udelay(20);
>> + } while (dma_ch_sts & channel_mask);
>
> This will hang hard if the hardware fails to respond for some reason,
> please have a timeout. A cpu_relax() would also be friendly.
>
I will modify this.
>> +#define DISABLE 0
>> +#define ENABLE 1
>
> Please don't do this :(
Ok.
>
>> +#define STATUS_SUCCESS 0
>> +#define STATUS_UNSUCCESSFUL -1
>
> Please use normal Linux error codes.
>
Ok.
> _______________________________________________
> Alsa-devel mailing list
> Alsa-devel at alsa-project.org
> http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
>
More information about the Alsa-devel
mailing list