From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
ACP IP can be powered on and off during system wide suspend/resume transition. AMD ASoC PCM device will use this module during system suspend/resume and PCM device's runtime pm.
Also, updated code comments.
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 --- drivers/gpu/drm/amd/acp/acp_hw.c | 220 +++++++++++++++++++++++------- drivers/gpu/drm/amd/acp/acp_hw.h | 19 +++ drivers/gpu/drm/amd/acp/include/amd_acp.h | 18 +++ 3 files changed, 204 insertions(+), 53 deletions(-)
diff --git a/drivers/gpu/drm/amd/acp/acp_hw.c b/drivers/gpu/drm/amd/acp/acp_hw.c index 34bb0fe..830a7da 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.c +++ b/drivers/gpu/drm/amd/acp/acp_hw.c @@ -40,17 +40,16 @@ #include <linux/delay.h> #include <linux/errno.h>
-#define VISLANDS30_IV_SRCID_ACP 0x000000a2 - #include "acp_gfx_if.h" #include "acp_hw.h"
#include "acp_2_2_d.h" #include "acp_2_2_sh_mask.h"
+#define VISLANDS30_IV_SRCID_ACP 0x000000a2 + /* Configure a given dma channel parameters - enable/disble, * number of descriptors, priority */ - static void config_acp_dma_channel(struct amd_acp_device *acp_dev, u8 ch_num, u16 dscr_strt_idx, u16 num_dscrs, enum acp_dma_priority_level priority_level) @@ -58,38 +57,30 @@ static void config_acp_dma_channel(struct amd_acp_device *acp_dev, u8 ch_num, u32 dma_ctrl; struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev;
- /* read the dma control register and disable the channel run field */ + /* disable the channel run field */ dma_ctrl = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CNTL_0 + ch_num); - /* clear the dma channel control bits */ dma_ctrl &= ~ACP_DMA_CNTL_0__DMAChRun_MASK; - cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_CNTL_0 + ch_num), dma_ctrl);
- /* there is no transfer happening on this channel so - * program DMAChDscrStrIdx to the index number of the first descriptor - * to be processed. - */ + /* program a DMA channel with first descriptor to be processed. */ cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_DSCR_STRT_IDX_0 + ch_num), (ACP_DMA_DSCR_STRT_IDX_0__DMAChDscrStrtIdx_MASK & dscr_strt_idx));
- /* program DMAChDscrDscrCnt to the number of descriptors to be - * processed in the transfer - */ + /* program a DMA channel with the number of descriptors to be + * processed in the transfer */ cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_DSCR_CNT_0 + ch_num), (ACP_DMA_DSCR_CNT_0__DMAChDscrCnt_MASK & num_dscrs));
- /* set DMAChPrioLvl according to the priority */ + /* set DMA channel priority */ cgs_write_register(acp_prv->cgs_device, (mmACP_DMA_PRIO_0 + ch_num), priority_level); }
- - /* Initialize the dma descriptors location in SRAM and page size */ static void acp_dma_descr_init(struct amd_acp_private *acp_prv) { @@ -143,7 +134,9 @@ static void config_dma_descriptor_in_sram(struct amd_acp_device *acp_dev, descr_info->size_xfer_dir.val); }
-/* Initialize the DMA descriptor information */ +/* Initialize the DMA descriptor information for transfer between + * system memory <-> ACP SRAM + */ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, u32 size, int direction, u32 pte_offset) @@ -215,7 +208,9 @@ static void set_acp_sysmem_dma_descriptors(struct amd_acp_device *acp_dev, } }
-/* Initialize the i2s dma descriptors in SRAM */ +/* Initialize the DMA descriptor information for transfer between + * ACP SRAM <-> I2S + */ static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev, u32 size, int direction) { @@ -226,9 +221,6 @@ static void set_acp_to_i2s_dma_descriptors(struct amd_acp_device *acp_dev,
num_descr = 2;
- /* Let I2s Know the direction of transfer and source/destination - * of data - */ dmadscr[0].size_xfer_dir.val = (u32) 0x0; if (direction == STREAM_PLAYBACK) { dma_dscr_idx = PLAYBACK_START_DMA_DESCR_CH13; @@ -304,7 +296,7 @@ static u16 get_dscr_idx(struct amd_acp_device *acp_dev, int direction)
}
-/* Create page table entries in ACP SRAM for the allocated memory */ +/* Create page table entries in ACP SRAM for the allocated memory */ static void acp_pte_config(struct amd_acp_device *acp_dev, struct page *pg, u16 num_of_pages, u32 pte_offset) { @@ -344,7 +336,6 @@ static void acp_pte_config(struct amd_acp_device *acp_dev, struct page *pg, } }
- /* enables/disables ACP's external interrupt */ static void acp_enable_external_interrupts(struct amd_acp_device *acp_dev, int enable) @@ -361,8 +352,8 @@ static void acp_enable_external_interrupts(struct amd_acp_device *acp_dev, mmACP_EXTERNAL_INTR_ENB, acp_ext_intr_enb); }
-/* Clear (acknowledge) DMA 'Interrupt on Complete' (IOC) in ACP - * external interrupt status register +/* Clear (acknowledge) DMA 'Interrupt on Complete' (IOC) in ACP + * external interrupt status register */ static void acp_ext_stat_clear_dmaioc(struct amd_acp_device *acp_dev, u8 ch_num) { @@ -382,7 +373,7 @@ static void acp_ext_stat_clear_dmaioc(struct amd_acp_device *acp_dev, u8 ch_num) } }
-/* Check whether interrupt (IOC) is generated or not */ +/* Check whether ACP DMA interrupt (IOC) is generated or not */ static u16 acp_get_intr_flag(struct amd_acp_device *acp_dev) { u32 ext_intr_status; @@ -412,7 +403,6 @@ static int irq_set_source(void *private_data, unsigned src_id, unsigned type, } }
- static inline void i2s_clear_irqs(struct amd_acp_device *acp_dev, int direction) { @@ -516,17 +506,17 @@ static void config_acp_dma(struct amd_acp_device *acp_dev, acp_pte_config(acp_dev, dma_config->pg, dma_config->num_of_pages, pte_offset);
- /* Configure System memory to acp dma descriptors */ + /* Configure System memory <-> ACP SRAM DMA descriptors */ set_acp_sysmem_dma_descriptors(acp_dev, dma_config->size, dma_config->direction, pte_offset);
- /* Configure acp to i2s dma descriptors */ + /* Configure ACP SRAM <-> I2S DMA descriptors */ set_acp_to_i2s_dma_descriptors(acp_dev, dma_config->size, dma_config->direction); }
-/* Start a given dma channel */ +/* Start a given DMA channel transfer */ static int acp_dma_start(struct amd_acp_device *acp_dev, u16 ch_num, bool is_circular) { @@ -572,7 +562,7 @@ static int acp_dma_start(struct amd_acp_device *acp_dev, return status; }
-/* Stop a given dma channel number*/ +/* Stop a given DMA channel transfer */ static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num) { int status = STATUS_UNSUCCESSFUL; @@ -584,7 +574,6 @@ static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num) if (acp_dev == NULL) return status;
- /* register mask value to check the channel status bits */ dma_ctrl = cgs_read_register(acp_prv->cgs_device, mmACP_DMA_CNTL_0 + ch_num);
@@ -636,6 +625,7 @@ static int acp_dma_stop(struct amd_acp_device *acp_dev, u8 ch_num) return status; }
+/* ACP DMA irq handler routine for playback, capture usecases */ static int dma_irq_handler(void *prv_data) { u16 play_acp_i2s_intr, cap_i2s_acp_intr, cap_acp_sysram_intr; @@ -655,7 +645,7 @@ static int dma_irq_handler(void *prv_data)
if (!play_acp_i2s_intr && !cap_i2s_acp_intr && !cap_acp_sysram_intr) { /* We registered for DMA Interrupt-On-Complete interrupts only. - * If we hit here, log, acknowledge it and return. */ + * If we hit here, log it and return. */ ext_intr_status = cgs_read_register(acp_prv->cgs_device, mmACP_EXTERNAL_INTR_STAT); pr_info("ACP: Not a DMA IOC irq: %x\n", ext_intr_status); @@ -702,16 +692,92 @@ static int irq_handler(void *private_data, unsigned src_id, return -1; }
+/* power off a tile/block within ACP */ +static void acp_suspend_tile(struct amd_acp_private *acp_prv, int tile) +{ + u32 val = 0; + u32 timeout = 0; + + if ((tile < ACP_TILE_P1) || (tile > ACP_TILE_DSP2)) { + pr_err(" %s : Invalid ACP power tile index\n", __func__); + return; + } + + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_READ_REG_0 + tile); + val &= ACP_TILE_ON_MASK; + + if (val == 0x0) { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_RETAIN_REG); + val = val | (1 << tile); + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_RETAIN_REG, + val); + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_CONFIG_REG, + 0x500 + tile); + + timeout = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (timeout--) { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_READ_REG_0 + tile); + val = val & ACP_TILE_ON_MASK; + if (val == ACP_TILE_OFF_MASK) + break; + } + + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_RETAIN_REG); + + val |= ACP_TILE_OFF_RETAIN_REG_MASK; + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_RETAIN_REG, + val); + } + +} + +/* power on a tile/block within ACP */ +static void acp_resume_tile(struct amd_acp_private *acp_prv, int tile) +{ + u32 val = 0; + u32 timeout = 0; + + if ((tile < ACP_TILE_P1) || (tile > ACP_TILE_DSP2)) { + pr_err(" %s : Invalid ACP power tile index\n", __func__); + return; + } + + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_READ_REG_0 + tile); + val = val & ACP_TILE_ON_MASK; + + if (val != 0x0) { + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_CONFIG_REG, + 0x600 + tile); + timeout = ACP_SOFT_RESET_DONE_TIME_OUT_VALUE; + while (timeout--) { + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_READ_REG_0 + tile); + val = val & ACP_TILE_ON_MASK; + if (val == 0x0) + break; + } + val = cgs_read_register(acp_prv->cgs_device, + mmACP_PGFSM_RETAIN_REG); + if (tile == ACP_TILE_P1) + val = val & (ACP_TILE_P1_MASK); + else if (tile == ACP_TILE_P2) + val = val & (ACP_TILE_P2_MASK); + + cgs_write_register(acp_prv->cgs_device, mmACP_PGFSM_RETAIN_REG, + val); + } +} + /* Initialize and bring ACP hardware to default state. */ -static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) +static void acp_init(struct amd_acp_private *acp_prv) { u32 val; u32 timeout_value; - int acp_hw_init_status = STATUS_UNSUCCESSFUL; - struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; - - if (acp_dev == NULL) - return acp_hw_init_status;
/* Assert Soft reset of ACP */ val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET); @@ -728,7 +794,7 @@ static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) break; }
- /* Enabling clock to ACP and waits until the clock is enabled */ + /* Enable clock to ACP and wait until the clock is enabled */ val = cgs_read_register(acp_prv->cgs_device, mmACP_CONTROL); val = val | ACP_CONTROL__ClkEn_MASK; cgs_write_register(acp_prv->cgs_device, mmACP_CONTROL, val); @@ -757,7 +823,6 @@ static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv)
acp_dma_descr_init(acp_prv);
- /* DMA DSCR BASE ADDRESS IN SRAM */ cgs_write_register(acp_prv->cgs_device, mmACP_DMA_DESC_BASE_ADDR, ACP_SRAM_BASE_ADDRESS);
@@ -768,20 +833,34 @@ static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) cgs_write_register(acp_prv->cgs_device, mmACP_EXTERNAL_INTR_CNTL, ACP_EXTERNAL_INTR_CNTL__DMAIOCMask_MASK);
+ pr_info("ACP: Initialized.\n"); + +} + +static int acp_hw_init(struct amd_acp_device *acp_dev, void *iprv) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + acp_init(acp_prv); + cgs_add_irq_source(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 1, - irq_set_source, irq_handler, iprv); + irq_set_source, irq_handler, iprv); + + /* Disable DSPs which are not used */ + acp_suspend_tile(acp_prv, ACP_TILE_DSP0); + acp_suspend_tile(acp_prv, ACP_TILE_DSP1); + acp_suspend_tile(acp_prv, ACP_TILE_DSP2);
- pr_info("ACP: Initialized.\n"); return STATUS_SUCCESS; }
-static void acp_hw_deinit(struct amd_acp_device *acp_dev) +/* Deintialize ACP */ +static void acp_deinit(struct amd_acp_private *acp_prv) { u32 val; u32 timeout_value; - struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev;
- /* Assert Soft reset of ACP */ + /* Assert Soft reset of ACP */ val = cgs_read_register(acp_prv->cgs_device, mmACP_SOFT_RESET);
val |= ACP_SOFT_RESET__SoftResetAud_MASK; @@ -795,7 +874,7 @@ static void acp_hw_deinit(struct amd_acp_device *acp_dev) break; } } - /** Disable ACP clock */ + /** Disable ACP clock */ val = cgs_read_register(acp_prv->cgs_device, mmACP_CONTROL); val &= ~ACP_CONTROL__ClkEn_MASK; cgs_write_register(acp_prv->cgs_device, mmACP_CONTROL, val); @@ -812,9 +891,15 @@ static void acp_hw_deinit(struct amd_acp_device *acp_dev) pr_info("ACP: De-Initialized.\n"); }
+static void acp_hw_deinit(struct amd_acp_device *acp_dev) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + acp_deinit(acp_prv); +}
-/* Get the number of bytes consumed for SRAM_TO_I2S DMA - * channel during rendering +/* Update DMA postion in audio ring buffer at period level granularity. + * This will be used by ALSA PCM driver */ static u32 acp_update_dma_pointer(struct amd_acp_device *acp_dev, int direction, u32 period_size) @@ -824,6 +909,7 @@ static u32 acp_update_dma_pointer(struct amd_acp_device *acp_dev, int direction, u32 mul; u32 dma_config; struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + pos = 0;
if (direction == STREAM_PLAYBACK) { @@ -850,8 +936,8 @@ static u32 acp_update_dma_pointer(struct amd_acp_device *acp_dev, int direction, return pos; }
-/* Wait for complete buffering to complete in HOST - * to SRAM DMA channel +/* Wait for initial buffering to complete in HOST to SRAM DMA channel + * for plaback usecase */ static void wait_for_prebuffer_finish(struct amd_acp_device *acp_dev) { @@ -927,6 +1013,22 @@ static void configure_i2s(struct amd_acp_device *acp_dev, configure_i2s_stream(acp_dev, i2s_config); }
+void amd_acp_pcm_suspend(struct amd_acp_device *acp_dev) +{ + struct amd_acp_private *acp_prv; + + acp_prv = (struct amd_acp_private *)acp_dev; + amd_acp_suspend(acp_prv); +} + +void amd_acp_pcm_resume(struct amd_acp_device *acp_dev) +{ + struct amd_acp_private *acp_prv; + + acp_prv = (struct amd_acp_private *)acp_dev; + amd_acp_resume(acp_prv); +} + int amd_acp_hw_init(void *cgs_device, unsigned acp_version_major, unsigned acp_version_minor, struct amd_acp_private **acp_private) @@ -962,6 +1064,9 @@ int amd_acp_hw_init(void *cgs_device, (*acp_private)->public.i2s_start = i2s_start; (*acp_private)->public.i2s_stop = i2s_stop;
+ (*acp_private)->public.acp_suspend = amd_acp_pcm_suspend; + (*acp_private)->public.acp_resume = amd_acp_pcm_resume; + return 0; }
@@ -973,10 +1078,19 @@ int amd_acp_hw_fini(struct amd_acp_private *acp_private)
void amd_acp_suspend(struct amd_acp_private *acp_private) { - /* TODO */ + acp_suspend_tile(acp_private, ACP_TILE_P2); + acp_suspend_tile(acp_private, ACP_TILE_P1); }
void amd_acp_resume(struct amd_acp_private *acp_private) { - /* TODO */ + acp_resume_tile(acp_private, ACP_TILE_P1); + acp_resume_tile(acp_private, ACP_TILE_P2); + + acp_init(acp_private); + + /* Disable DSPs which are not going to be used */ + acp_suspend_tile(acp_private, ACP_TILE_DSP0); + acp_suspend_tile(acp_private, ACP_TILE_DSP1); + acp_suspend_tile(acp_private, ACP_TILE_DSP2); } diff --git a/drivers/gpu/drm/amd/acp/acp_hw.h b/drivers/gpu/drm/amd/acp/acp_hw.h index 5e7e225..4aa6b1c 100644 --- a/drivers/gpu/drm/amd/acp/acp_hw.h +++ b/drivers/gpu/drm/amd/acp/acp_hw.h @@ -40,6 +40,25 @@ #define FROM_ACP_I2S_1 0xa #define FROM_ACP_I2S_2 0xb
+#define ACP_TILE_ON_MASK 0x03 +#define ACP_TILE_OFF_MASK 0x02 +#define ACP_TILE_ON_RETAIN_REG_MASK 0x1f +#define ACP_TILE_OFF_RETAIN_REG_MASK 0x20 + +#define ACP_TILE_P1_MASK 0x3e +#define ACP_TILE_P2_MASK 0x3d +#define ACP_TILE_DSP0_MASK 0x3b +#define ACP_TILE_DSP1_MASK 0x37 +#define ACP_TILE_DSP2_MASK 0x2f + +enum { + ACP_TILE_P1 = 0, + ACP_TILE_P2, + ACP_TILE_DSP0, + ACP_TILE_DSP1, + ACP_TILE_DSP2, +}; + enum { STREAM_PLAYBACK = 0, STREAM_CAPTURE, diff --git a/drivers/gpu/drm/amd/acp/include/amd_acp.h b/drivers/gpu/drm/amd/acp/include/amd_acp.h index 988d72a..8c1d31a 100644 --- a/drivers/gpu/drm/amd/acp/include/amd_acp.h +++ b/drivers/gpu/drm/amd/acp/include/amd_acp.h @@ -189,8 +189,26 @@ struct amd_acp_device { */ void (*i2s_stop)(struct amd_acp_device *acp_dev, int direction);
+ /** + * acp_suspend() - Power off ACP + * @acp_dev: acp device + * + * Switch off power tiles of ACP + */ + void (*acp_suspend)(struct amd_acp_device *acp_dev); + + /** + * acp_resume() - Power on ACP + * @acp_dev: acp device + * + * Switch on power tiles of ACP + */ + + void (*acp_resume)(struct amd_acp_device *acp_dev); + /* TODO: Need callback registration interface for asynchronous * notifications */ + };
#endif /* _AMD_ACP_H */