[alsa-devel] [PATCH 0/5] Add ASoC support for AMD APUs [v4]
This patch set implements support for i2s audio and new AMD GPUs. The i2s codec is fed by a DMA engine on the GPU. To handle this we create mfd cells which we hang the i2s codec and DMA engine on. Because of this, this patch set covers two subsystems: drm and alsa. The drm patches add support for the ACP hw block which provides the DMA engine for the i2s codec. The alsa patches add the ASoC driver for the i2s codec. Since the alsa changes depend on the drm changes in this patch set as well as some other drm changes queued for 4.3, I'd like to take the alsa patches in via the drm tree.
V2 changes: - Use the MFD subsystem rather than adding our own bus - Squash all sub-feature patches together - fix comments mentioned in previous review
V3 changes: - Update the designware driver to handle slave mode, amd specific features - Use the designware driver directly for i2s - Move the DMA handling from the GPU driver into the AMD ASoC driver - Change the license on the ASoC driver to GPL
V4 changes: - get the master vs slave mode for the designware codec from registers - copy designware quirks from platform data in local struct
Patch 4 adds the register headers for the ACP block which is a pretty big patch so I've excluded it from email. The entire patch set can be viewed here: http://cgit.freedesktop.org/~agd5f/linux/log/?h=acp-upstream4
Thanks,
Alex
Maruthi Bayyavarapu (1): drm/amd: add ACP driver support
Maruthi Srinivas Bayyavarapu (4): ASoC : dwc : support dw i2s in slave mode ASoC : dwc : support dw i2s in AMD platform ASoC : AMD : add ACP 2.2 register headers ASoC: AMD: add AMD ASoC ACP-I2S driver
drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/amd/acp/Kconfig | 10 + drivers/gpu/drm/amd/acp/Makefile | 9 + drivers/gpu/drm/amd/acp/acp_hw.c | 127 ++ drivers/gpu/drm/amd/acp/include/acp_gfx_if.h | 49 + drivers/gpu/drm/amd/amdgpu/Makefile | 13 +- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 12 + drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 269 +++ drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h | 41 + drivers/gpu/drm/amd/amdgpu/vi.c | 12 + drivers/gpu/drm/amd/include/amd_shared.h | 1 + include/linux/mfd/amd_acp.h | 43 + include/sound/designware_i2s.h | 5 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/amd/Kconfig | 4 + sound/soc/amd/Makefile | 3 + sound/soc/amd/acp-pcm-dma.c | 518 ++++++ sound/soc/amd/acp.c | 736 +++++++++ sound/soc/amd/acp.h | 147 ++ sound/soc/amd/include/acp_2_2_d.h | 609 +++++++ sound/soc/amd/include/acp_2_2_enum.h | 1068 ++++++++++++ sound/soc/amd/include/acp_2_2_sh_mask.h | 2292 ++++++++++++++++++++++++++ sound/soc/dwc/designware_i2s.c | 276 ++-- 24 files changed, 6146 insertions(+), 102 deletions(-) create mode 100644 drivers/gpu/drm/amd/acp/Kconfig create mode 100644 drivers/gpu/drm/amd/acp/Makefile create mode 100644 drivers/gpu/drm/amd/acp/acp_hw.c create mode 100644 drivers/gpu/drm/amd/acp/include/acp_gfx_if.h create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h create mode 100644 include/linux/mfd/amd_acp.h create mode 100644 sound/soc/amd/Kconfig create mode 100644 sound/soc/amd/Makefile create mode 100644 sound/soc/amd/acp-pcm-dma.c create mode 100644 sound/soc/amd/acp.c create mode 100644 sound/soc/amd/acp.h create mode 100644 sound/soc/amd/include/acp_2_2_d.h create mode 100644 sound/soc/amd/include/acp_2_2_enum.h create mode 100644 sound/soc/amd/include/acp_2_2_sh_mask.h
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
dw i2s controller can work in slave mode, codec being master. dw i2s is made to support master/slave operation, by reading dwc register.
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Signed-off-by: Alex Deucher alexander.deucher@amd.com ---
v2: check register to determine master/slave mode
include/sound/designware_i2s.h | 2 + sound/soc/dwc/designware_i2s.c | 92 +++++++++++++++++++++++++----------------- 2 files changed, 57 insertions(+), 37 deletions(-)
diff --git a/include/sound/designware_i2s.h b/include/sound/designware_i2s.h index 3a8fca9..8966ba7 100644 --- a/include/sound/designware_i2s.h +++ b/include/sound/designware_i2s.h @@ -38,6 +38,8 @@ struct i2s_clk_config_data { struct i2s_platform_data { #define DWC_I2S_PLAY (1 << 0) #define DWC_I2S_RECORD (1 << 1) + #define DW_I2S_SLAVE (1 << 2) + #define DW_I2S_MASTER (1 << 3) unsigned int cap; int channel; u32 snd_fmts; diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index a3e97b4..3a52f82 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -273,23 +273,25 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
config->sample_rate = params_rate(params);
- if (dev->i2s_clk_cfg) { - ret = dev->i2s_clk_cfg(config); - if (ret < 0) { - dev_err(dev->dev, "runtime audio clk config fail\n"); - return ret; - } - } else { - u32 bitclk = config->sample_rate * config->data_width * 2; - - ret = clk_set_rate(dev->clk, bitclk); - if (ret) { - dev_err(dev->dev, "Can't set I2S clock rate: %d\n", - ret); - return ret; + if (dev->capability & DW_I2S_MASTER) { + if (dev->i2s_clk_cfg) { + ret = dev->i2s_clk_cfg(config); + if (ret < 0) { + dev_err(dev->dev, "runtime audio clk config fail\n"); + return ret; + } + } else { + u32 bitclk = config->sample_rate * + config->data_width * 2; + + ret = clk_set_rate(dev->clk, bitclk); + if (ret) { + dev_err(dev->dev, "Can't set I2S clock rate: %d\n", + ret); + return ret; + } } } - return 0; }
@@ -357,7 +359,8 @@ static int dw_i2s_suspend(struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
- clk_disable(dev->clk); + if (dev->capability & DW_I2S_MASTER) + clk_disable(dev->clk); return 0; }
@@ -365,7 +368,8 @@ static int dw_i2s_resume(struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
- clk_enable(dev->clk); + if (dev->capability & DW_I2S_MASTER) + clk_enable(dev->clk); return 0; }
@@ -443,6 +447,14 @@ static int dw_configure_dai(struct dw_i2s_dev *dev, dw_i2s_dai->capture.rates = rates; }
+ if (COMP1_MODE_EN(comp1)) { + dev_dbg(dev->dev, "designware: i2s master mode supported\n"); + dev->capability |= DW_I2S_MASTER; + } else { + dev_dbg(dev->dev, "designware: i2s slave mode supported\n"); + dev->capability |= DW_I2S_SLAVE; + } + return 0; }
@@ -529,6 +541,7 @@ static int dw_i2s_probe(struct platform_device *pdev) struct resource *res; int ret; struct snd_soc_dai_driver *dw_i2s_dai; + const char *clk_id;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) { @@ -550,32 +563,35 @@ static int dw_i2s_probe(struct platform_device *pdev) return PTR_ERR(dev->i2s_base);
dev->dev = &pdev->dev; + if (pdata) { + dev->capability = pdata->cap; + clk_id = NULL; ret = dw_configure_dai_by_pd(dev, dw_i2s_dai, res, pdata); - if (ret < 0) - return ret; + } else { + clk_id = "i2sclk"; + ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res); + } + if (ret < 0) + return ret;
- dev->capability = pdata->cap; - dev->i2s_clk_cfg = pdata->i2s_clk_cfg; - if (!dev->i2s_clk_cfg) { - dev_err(&pdev->dev, "no clock configure method\n"); - return -ENODEV; + if (dev->capability & DW_I2S_MASTER) { + if (pdata) { + dev->i2s_clk_cfg = pdata->i2s_clk_cfg; + if (!dev->i2s_clk_cfg) { + dev_err(&pdev->dev, "no clock configure method\n"); + return -ENODEV; + } } + dev->clk = devm_clk_get(&pdev->dev, clk_id);
- dev->clk = devm_clk_get(&pdev->dev, NULL); - } else { - ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res); + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + ret = clk_prepare_enable(dev->clk); if (ret < 0) return ret; - - dev->clk = devm_clk_get(&pdev->dev, "i2sclk"); } - if (IS_ERR(dev->clk)) - return PTR_ERR(dev->clk); - - ret = clk_prepare_enable(dev->clk); - if (ret < 0) - return ret;
dev_set_drvdata(&pdev->dev, dev); ret = devm_snd_soc_register_component(&pdev->dev, &dw_i2s_component, @@ -597,7 +613,8 @@ static int dw_i2s_probe(struct platform_device *pdev) return 0;
err_clk_disable: - clk_disable_unprepare(dev->clk); + if (dev->capability & DW_I2S_MASTER) + clk_disable_unprepare(dev->clk); return ret; }
@@ -605,7 +622,8 @@ static int dw_i2s_remove(struct platform_device *pdev) { struct dw_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
- clk_disable_unprepare(dev->clk); + if (dev->capability & DW_I2S_MASTER) + clk_disable_unprepare(dev->clk);
return 0; }
On Fri, Sep 25, 2015 at 05:48:22PM -0400, Alex Deucher wrote:
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
dw i2s controller can work in slave mode, codec being master. dw i2s is made to support master/slave operation, by reading dwc register.
I'll apply this but can you please send a followup implementing a _dai_fmt() operation that checks to make sure that the mode we're setting corresponds to what we read back from the hardware.
On Mon, Oct 5, 2015 at 9:01 PM, Mark Brown broonie@kernel.org wrote:
On Fri, Sep 25, 2015 at 05:48:22PM -0400, Alex Deucher wrote:
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
dw i2s controller can work in slave mode, codec being master. dw i2s is made to support master/slave operation, by reading dwc register.
I'll apply this but can you please send a followup implementing a _dai_fmt() operation that checks to make sure that the mode we're setting corresponds to what we read back from the hardware.
Ok, I will send.
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
The patch
ASoC: dwc: support dw i2s in slave mode
has been applied to the asoc tree at
git://git.kernel.org/pub/scm/linux/kernel/git/broonie/sound.git
All being well this means that it will be integrated into the linux-next tree (usually sometime in the next 24 hours) and sent to Linus during the next merge window (or sooner if it is a bug fix), however if problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing and review of the tree, please engage with people reporting problems and send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they should be sent as incremental updates against current git, existing patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying to this mail.
Thanks, Mark
From 1d957d862ac782eaf5803d4d4cf167708e4dc147 Mon Sep 17 00:00:00 2001
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com Date: Fri, 25 Sep 2015 17:48:22 -0400 Subject: [PATCH] ASoC: dwc: support dw i2s in slave mode
dw i2s controller can work in slave mode, codec being master. dw i2s is made to support master/slave operation, by reading dwc register.
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Signed-off-by: Alex Deucher alexander.deucher@amd.com Signed-off-by: Mark Brown broonie@kernel.org --- include/sound/designware_i2s.h | 2 + sound/soc/dwc/designware_i2s.c | 92 +++++++++++++++++++++++++----------------- 2 files changed, 57 insertions(+), 37 deletions(-)
diff --git a/include/sound/designware_i2s.h b/include/sound/designware_i2s.h index 3a8fca9..8966ba7 100644 --- a/include/sound/designware_i2s.h +++ b/include/sound/designware_i2s.h @@ -38,6 +38,8 @@ struct i2s_clk_config_data { struct i2s_platform_data { #define DWC_I2S_PLAY (1 << 0) #define DWC_I2S_RECORD (1 << 1) + #define DW_I2S_SLAVE (1 << 2) + #define DW_I2S_MASTER (1 << 3) unsigned int cap; int channel; u32 snd_fmts; diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index a3e97b4..3a52f82 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -273,23 +273,25 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream,
config->sample_rate = params_rate(params);
- if (dev->i2s_clk_cfg) { - ret = dev->i2s_clk_cfg(config); - if (ret < 0) { - dev_err(dev->dev, "runtime audio clk config fail\n"); - return ret; - } - } else { - u32 bitclk = config->sample_rate * config->data_width * 2; - - ret = clk_set_rate(dev->clk, bitclk); - if (ret) { - dev_err(dev->dev, "Can't set I2S clock rate: %d\n", - ret); - return ret; + if (dev->capability & DW_I2S_MASTER) { + if (dev->i2s_clk_cfg) { + ret = dev->i2s_clk_cfg(config); + if (ret < 0) { + dev_err(dev->dev, "runtime audio clk config fail\n"); + return ret; + } + } else { + u32 bitclk = config->sample_rate * + config->data_width * 2; + + ret = clk_set_rate(dev->clk, bitclk); + if (ret) { + dev_err(dev->dev, "Can't set I2S clock rate: %d\n", + ret); + return ret; + } } } - return 0; }
@@ -357,7 +359,8 @@ static int dw_i2s_suspend(struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
- clk_disable(dev->clk); + if (dev->capability & DW_I2S_MASTER) + clk_disable(dev->clk); return 0; }
@@ -365,7 +368,8 @@ static int dw_i2s_resume(struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
- clk_enable(dev->clk); + if (dev->capability & DW_I2S_MASTER) + clk_enable(dev->clk); return 0; }
@@ -443,6 +447,14 @@ static int dw_configure_dai(struct dw_i2s_dev *dev, dw_i2s_dai->capture.rates = rates; }
+ if (COMP1_MODE_EN(comp1)) { + dev_dbg(dev->dev, "designware: i2s master mode supported\n"); + dev->capability |= DW_I2S_MASTER; + } else { + dev_dbg(dev->dev, "designware: i2s slave mode supported\n"); + dev->capability |= DW_I2S_SLAVE; + } + return 0; }
@@ -529,6 +541,7 @@ static int dw_i2s_probe(struct platform_device *pdev) struct resource *res; int ret; struct snd_soc_dai_driver *dw_i2s_dai; + const char *clk_id;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); if (!dev) { @@ -550,32 +563,35 @@ static int dw_i2s_probe(struct platform_device *pdev) return PTR_ERR(dev->i2s_base);
dev->dev = &pdev->dev; + if (pdata) { + dev->capability = pdata->cap; + clk_id = NULL; ret = dw_configure_dai_by_pd(dev, dw_i2s_dai, res, pdata); - if (ret < 0) - return ret; + } else { + clk_id = "i2sclk"; + ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res); + } + if (ret < 0) + return ret;
- dev->capability = pdata->cap; - dev->i2s_clk_cfg = pdata->i2s_clk_cfg; - if (!dev->i2s_clk_cfg) { - dev_err(&pdev->dev, "no clock configure method\n"); - return -ENODEV; + if (dev->capability & DW_I2S_MASTER) { + if (pdata) { + dev->i2s_clk_cfg = pdata->i2s_clk_cfg; + if (!dev->i2s_clk_cfg) { + dev_err(&pdev->dev, "no clock configure method\n"); + return -ENODEV; + } } + dev->clk = devm_clk_get(&pdev->dev, clk_id);
- dev->clk = devm_clk_get(&pdev->dev, NULL); - } else { - ret = dw_configure_dai_by_dt(dev, dw_i2s_dai, res); + if (IS_ERR(dev->clk)) + return PTR_ERR(dev->clk); + + ret = clk_prepare_enable(dev->clk); if (ret < 0) return ret; - - dev->clk = devm_clk_get(&pdev->dev, "i2sclk"); } - if (IS_ERR(dev->clk)) - return PTR_ERR(dev->clk); - - ret = clk_prepare_enable(dev->clk); - if (ret < 0) - return ret;
dev_set_drvdata(&pdev->dev, dev); ret = devm_snd_soc_register_component(&pdev->dev, &dw_i2s_component, @@ -597,7 +613,8 @@ static int dw_i2s_probe(struct platform_device *pdev) return 0;
err_clk_disable: - clk_disable_unprepare(dev->clk); + if (dev->capability & DW_I2S_MASTER) + clk_disable_unprepare(dev->clk); return ret; }
@@ -605,7 +622,8 @@ static int dw_i2s_remove(struct platform_device *pdev) { struct dw_i2s_dev *dev = dev_get_drvdata(&pdev->dev);
- clk_disable_unprepare(dev->clk); + if (dev->capability & DW_I2S_MASTER) + clk_disable_unprepare(dev->clk);
return 0; }
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
Vendor specific quirk was added to: 1. Support AMD platform which has two dwc controllers with different base address for playback and capture. Also, I2S_COMP_PARAM_* registers offsets differed. 2. Resume audio which was active before system suspend. After 'resume', dwc need to be reconfigured with params configured during 'hw_params' callback. With this, audio usecase continues from where it got stopped because of 'suspend'.
Signed-off-by: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Signed-off-by: Alex Deucher alexander.deucher@amd.com ---
v2: Copy quirks from platform data to local struct since platform data may not be available in some use cases.
include/sound/designware_i2s.h | 3 + sound/soc/dwc/designware_i2s.c | 190 ++++++++++++++++++++++++++--------------- 2 files changed, 126 insertions(+), 67 deletions(-)
diff --git a/include/sound/designware_i2s.h b/include/sound/designware_i2s.h index 8966ba7..7e8094a 100644 --- a/include/sound/designware_i2s.h +++ b/include/sound/designware_i2s.h @@ -44,6 +44,9 @@ struct i2s_platform_data { int channel; u32 snd_fmts; u32 snd_rates; + #define DW_I2S_VENDOR_AMD (1 << 0) + unsigned int quirks; +
void *play_dma_data; void *capture_dma_data; diff --git a/sound/soc/dwc/designware_i2s.c b/sound/soc/dwc/designware_i2s.c index 3a52f82..dce1e4a 100644 --- a/sound/soc/dwc/designware_i2s.c +++ b/sound/soc/dwc/designware_i2s.c @@ -89,11 +89,15 @@ union dw_i2s_snd_dma_data { };
struct dw_i2s_dev { - void __iomem *i2s_base; + void __iomem *i2s_pbase; + void __iomem *i2s_cbase; struct clk *clk; int active; unsigned int capability; + unsigned int quirks; struct device *dev; + u32 ccr; + u32 xfer_resolution;
/* data related to DMA transfers b/w i2s and DMAC */ union dw_i2s_snd_dma_data play_dma_data; @@ -118,10 +122,10 @@ static inline void i2s_disable_channels(struct dw_i2s_dev *dev, u32 stream)
if (stream == SNDRV_PCM_STREAM_PLAYBACK) { for (i = 0; i < 4; i++) - i2s_write_reg(dev->i2s_base, TER(i), 0); + i2s_write_reg(dev->i2s_pbase, TER(i), 0); } else { for (i = 0; i < 4; i++) - i2s_write_reg(dev->i2s_base, RER(i), 0); + i2s_write_reg(dev->i2s_cbase, RER(i), 0); } }
@@ -131,25 +135,25 @@ static inline void i2s_clear_irqs(struct dw_i2s_dev *dev, u32 stream)
if (stream == SNDRV_PCM_STREAM_PLAYBACK) { for (i = 0; i < 4; i++) - i2s_write_reg(dev->i2s_base, TOR(i), 0); + i2s_write_reg(dev->i2s_pbase, TOR(i), 0); } else { for (i = 0; i < 4; i++) - i2s_write_reg(dev->i2s_base, ROR(i), 0); + i2s_write_reg(dev->i2s_cbase, ROR(i), 0); } }
static void i2s_start(struct dw_i2s_dev *dev, struct snd_pcm_substream *substream) { - - i2s_write_reg(dev->i2s_base, IER, 1); - - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - i2s_write_reg(dev->i2s_base, ITER, 1); - else - i2s_write_reg(dev->i2s_base, IRER, 1); - - i2s_write_reg(dev->i2s_base, CER, 1); + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->i2s_pbase, IER, 1); + i2s_write_reg(dev->i2s_pbase, ITER, 1); + i2s_write_reg(dev->i2s_pbase, CER, 1); + } else { + i2s_write_reg(dev->i2s_cbase, IER, 1); + i2s_write_reg(dev->i2s_cbase, IRER, 1); + i2s_write_reg(dev->i2s_cbase, CER, 1); + } }
static void i2s_stop(struct dw_i2s_dev *dev, @@ -159,24 +163,36 @@ static void i2s_stop(struct dw_i2s_dev *dev,
i2s_clear_irqs(dev, substream->stream); if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - i2s_write_reg(dev->i2s_base, ITER, 0); - + i2s_write_reg(dev->i2s_pbase, ITER, 0); for (i = 0; i < 4; i++) { - irq = i2s_read_reg(dev->i2s_base, IMR(i)); - i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x30); + irq = i2s_read_reg(dev->i2s_pbase, IMR(i)); + i2s_write_reg(dev->i2s_pbase, IMR(i), + irq | 0x30); } } else { - i2s_write_reg(dev->i2s_base, IRER, 0); + i2s_write_reg(dev->i2s_cbase, IRER, 0);
for (i = 0; i < 4; i++) { - irq = i2s_read_reg(dev->i2s_base, IMR(i)); - i2s_write_reg(dev->i2s_base, IMR(i), irq | 0x03); + irq = i2s_read_reg(dev->i2s_cbase, IMR(i)); + i2s_write_reg(dev->i2s_cbase, IMR(i), + irq | 0x03); } }
- if (!dev->active) { - i2s_write_reg(dev->i2s_base, CER, 0); - i2s_write_reg(dev->i2s_base, IER, 0); + if (dev->quirks & DW_I2S_VENDOR_AMD) { + if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->i2s_pbase, CER, 0); + i2s_write_reg(dev->i2s_pbase, IER, 0); + } else { + i2s_write_reg(dev->i2s_cbase, CER, 0); + i2s_write_reg(dev->i2s_cbase, IER, 0); + } + + } else { + if (!dev->active) { + i2s_write_reg(dev->i2s_pbase, CER, 0); + i2s_write_reg(dev->i2s_pbase, IER, 0); + } } }
@@ -204,31 +220,59 @@ static int dw_i2s_startup(struct snd_pcm_substream *substream, return 0; }
+static void dw_i2s_config(struct dw_i2s_dev *dev, int stream) +{ + u32 ch_reg, irq; + struct i2s_clk_config_data *config = &dev->config; + + + i2s_disable_channels(dev, stream); + + for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) { + if (stream == SNDRV_PCM_STREAM_PLAYBACK) { + i2s_write_reg(dev->i2s_pbase, TCR(ch_reg), + dev->xfer_resolution); + i2s_write_reg(dev->i2s_pbase, TFCR(ch_reg), 0x02); + irq = i2s_read_reg(dev->i2s_pbase, IMR(ch_reg)); + i2s_write_reg(dev->i2s_pbase, IMR(ch_reg), irq & ~0x30); + i2s_write_reg(dev->i2s_pbase, TER(ch_reg), 1); + } else { + i2s_write_reg(dev->i2s_cbase, RCR(ch_reg), + dev->xfer_resolution); + i2s_write_reg(dev->i2s_cbase, RFCR(ch_reg), 0x07); + irq = i2s_read_reg(dev->i2s_cbase, IMR(ch_reg)); + i2s_write_reg(dev->i2s_cbase, IMR(ch_reg), irq & ~0x03); + i2s_write_reg(dev->i2s_cbase, RER(ch_reg), 1); + } + + } +} + static int dw_i2s_hw_params(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *params, struct snd_soc_dai *dai) { struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai); struct i2s_clk_config_data *config = &dev->config; - u32 ccr, xfer_resolution, ch_reg, irq; + int ret;
switch (params_format(params)) { case SNDRV_PCM_FORMAT_S16_LE: config->data_width = 16; - ccr = 0x00; - xfer_resolution = 0x02; + dev->ccr = 0x00; + dev->xfer_resolution = 0x02; break;
case SNDRV_PCM_FORMAT_S24_LE: config->data_width = 24; - ccr = 0x08; - xfer_resolution = 0x04; + dev->ccr = 0x08; + dev->xfer_resolution = 0x04; break;
case SNDRV_PCM_FORMAT_S32_LE: config->data_width = 32; - ccr = 0x10; - xfer_resolution = 0x05; + dev->ccr = 0x10; + dev->xfer_resolution = 0x05; break;
default: @@ -249,31 +293,12 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, return -EINVAL; }
- i2s_disable_channels(dev, substream->stream); - - for (ch_reg = 0; ch_reg < (config->chan_nr / 2); ch_reg++) { - if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) { - i2s_write_reg(dev->i2s_base, TCR(ch_reg), - xfer_resolution); - i2s_write_reg(dev->i2s_base, TFCR(ch_reg), 0x02); - irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg)); - i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x30); - i2s_write_reg(dev->i2s_base, TER(ch_reg), 1); - } else { - i2s_write_reg(dev->i2s_base, RCR(ch_reg), - xfer_resolution); - i2s_write_reg(dev->i2s_base, RFCR(ch_reg), 0x07); - irq = i2s_read_reg(dev->i2s_base, IMR(ch_reg)); - i2s_write_reg(dev->i2s_base, IMR(ch_reg), irq & ~0x03); - i2s_write_reg(dev->i2s_base, RER(ch_reg), 1); - } - } - - i2s_write_reg(dev->i2s_base, CCR, ccr); + dw_i2s_config(dev, substream->stream);
config->sample_rate = params_rate(params);
if (dev->capability & DW_I2S_MASTER) { + i2s_write_reg(dev->i2s_pbase, CCR, dev->ccr); if (dev->i2s_clk_cfg) { ret = dev->i2s_clk_cfg(config); if (ret < 0) { @@ -287,7 +312,7 @@ static int dw_i2s_hw_params(struct snd_pcm_substream *substream, ret = clk_set_rate(dev->clk, bitclk); if (ret) { dev_err(dev->dev, "Can't set I2S clock rate: %d\n", - ret); + ret); return ret; } } @@ -307,9 +332,9 @@ static int dw_i2s_prepare(struct snd_pcm_substream *substream, struct dw_i2s_dev *dev = snd_soc_dai_get_drvdata(dai);
if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) - i2s_write_reg(dev->i2s_base, TXFFR, 1); + i2s_write_reg(dev->i2s_pbase, TXFFR, 1); else - i2s_write_reg(dev->i2s_base, RXFFR, 1); + i2s_write_reg(dev->i2s_cbase, RXFFR, 1);
return 0; } @@ -370,6 +395,13 @@ static int dw_i2s_resume(struct snd_soc_dai *dai)
if (dev->capability & DW_I2S_MASTER) clk_enable(dev->clk); + + if (dev->quirks & DW_I2S_VENDOR_AMD) { + if (dai->playback_active) + dw_i2s_config(dev, SNDRV_PCM_STREAM_PLAYBACK); + if (dai->capture_active) + dw_i2s_config(dev, SNDRV_PCM_STREAM_CAPTURE); + } return 0; }
@@ -419,9 +451,15 @@ static int dw_configure_dai(struct dw_i2s_dev *dev, * Read component parameter registers to extract * the I2S block's configuration. */ - u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1); - u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2); - u32 idx; + u32 comp1, comp2, idx; + + if (dev->quirks & DW_I2S_VENDOR_AMD) { + comp1 = i2s_read_reg(dev->i2s_pbase, 0x124); + comp2 = i2s_read_reg(dev->i2s_pbase, 0x120); + } else { + comp1 = i2s_read_reg(dev->i2s_pbase, I2S_COMP_PARAM_1); + comp2 = i2s_read_reg(dev->i2s_pbase, I2S_COMP_PARAM_2); + }
if (COMP1_TX_ENABLED(comp1)) { dev_dbg(dev->dev, " designware: play supported\n"); @@ -463,10 +501,16 @@ static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev, struct resource *res, const struct i2s_platform_data *pdata) { - u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1); - u32 idx = COMP1_APB_DATA_WIDTH(comp1); + u32 comp1, idx; int ret;
+ if (dev->quirks & DW_I2S_VENDOR_AMD) + comp1 = i2s_read_reg(dev->i2s_pbase, 0x6c); + else + comp1 = i2s_read_reg(dev->i2s_pbase, I2S_COMP_PARAM_1); + + idx = COMP1_APB_DATA_WIDTH(comp1); + if (WARN_ON(idx >= ARRAY_SIZE(bus_widths))) return -EINVAL;
@@ -474,6 +518,9 @@ static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev, if (ret < 0) return ret;
+ if (dev->quirks & DW_I2S_VENDOR_AMD) + goto end; + /* Set DMA slaves info */ dev->play_dma_data.pd.data = pdata->play_dma_data; dev->capture_dma_data.pd.data = pdata->capture_dma_data; @@ -485,7 +532,7 @@ static int dw_configure_dai_by_pd(struct dw_i2s_dev *dev, dev->capture_dma_data.pd.addr_width = bus_widths[idx]; dev->play_dma_data.pd.filter = pdata->filter; dev->capture_dma_data.pd.filter = pdata->filter; - +end: return 0; }
@@ -493,8 +540,8 @@ static int dw_configure_dai_by_dt(struct dw_i2s_dev *dev, struct snd_soc_dai_driver *dw_i2s_dai, struct resource *res) { - u32 comp1 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_1); - u32 comp2 = i2s_read_reg(dev->i2s_base, I2S_COMP_PARAM_2); + u32 comp1 = i2s_read_reg(dev->i2s_pbase, I2S_COMP_PARAM_1); + u32 comp2 = i2s_read_reg(dev->i2s_pbase, I2S_COMP_PARAM_2); u32 fifo_depth = 1 << (1 + COMP1_FIFO_DEPTH_GLOBAL(comp1)); u32 idx = COMP1_APB_DATA_WIDTH(comp1); u32 idx2; @@ -558,14 +605,15 @@ static int dw_i2s_probe(struct platform_device *pdev) dw_i2s_dai->resume = dw_i2s_resume;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - dev->i2s_base = devm_ioremap_resource(&pdev->dev, res); - if (IS_ERR(dev->i2s_base)) - return PTR_ERR(dev->i2s_base); + dev->i2s_pbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->i2s_pbase)) + return PTR_ERR(dev->i2s_pbase);
- dev->dev = &pdev->dev; + dev->i2s_cbase = dev->i2s_pbase;
if (pdata) { dev->capability = pdata->cap; + dev->quirks = pdata->quirks; clk_id = NULL; ret = dw_configure_dai_by_pd(dev, dw_i2s_dai, res, pdata); } else { @@ -575,6 +623,13 @@ static int dw_i2s_probe(struct platform_device *pdev) if (ret < 0) return ret;
+ if (dev->quirks & DW_I2S_VENDOR_AMD) { + res = platform_get_resource(pdev, IORESOURCE_MEM, 1); + dev->i2s_cbase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->i2s_cbase)) + return PTR_ERR(dev->i2s_cbase); + } + if (dev->capability & DW_I2S_MASTER) { if (pdata) { dev->i2s_clk_cfg = pdata->i2s_clk_cfg; @@ -593,6 +648,7 @@ static int dw_i2s_probe(struct platform_device *pdev) return ret; }
+ dev->dev = &pdev->dev; dev_set_drvdata(&pdev->dev, dev); ret = devm_snd_soc_register_component(&pdev->dev, &dw_i2s_component, dw_i2s_dai, 1);
On Fri, Sep 25, 2015 at 05:48:23PM -0400, Alex Deucher wrote:
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
Vendor specific quirk was added to:
- Support AMD platform which has two dwc controllers with different base address for playback and capture. Also, I2S_COMP_PARAM_* registers offsets differed.
- Resume audio which was active before system suspend. After 'resume', dwc need to be reconfigured with params configured during 'hw_params' callback. With this, audio usecase continues from where it got stopped because of 'suspend'.
This is hard to review since there are several different changes in here, one change per commit is muuch more helpful. As is I'm not 100% sure exactly what each bit of the change is intended to do. One example here is that a separate patch splitting pbase and cbase would be really helpful.
--- a/include/sound/designware_i2s.h +++ b/include/sound/designware_i2s.h @@ -44,6 +44,9 @@ struct i2s_platform_data { int channel; u32 snd_fmts; u32 snd_rates;
- #define DW_I2S_VENDOR_AMD (1 << 0)
- unsigned int quirks;
I would expect to see a set of quirks with the AMD devices selecting those that apply to them rather than just a per vendor define - this is more generic and more maintainable long term. AMD may require different sets of quirks with some future device and systems from other vendors may require some but not all of the quirks that AMD requires.
- if (dev->quirks & DW_I2S_VENDOR_AMD) {
comp1 = i2s_read_reg(dev->i2s_pbase, 0x124);
comp2 = i2s_read_reg(dev->i2s_pbase, 0x120);
Please add defines for these registers. Rather than having the quirk code throughout the driver it might be clearer to have the comp1 and comp2 register offsets stored in the driver data so you can just do the actual quirk once at probe time.
On Mon, Oct 5, 2015 at 9:11 PM, Mark Brown broonie@kernel.org wrote:
On Fri, Sep 25, 2015 at 05:48:23PM -0400, Alex Deucher wrote:
From: Maruthi Srinivas Bayyavarapu Maruthi.Bayyavarapu@amd.com
Vendor specific quirk was added to:
- Support AMD platform which has two dwc controllers with different base address for playback and capture. Also, I2S_COMP_PARAM_* registers offsets differed.
- Resume audio which was active before system suspend. After 'resume', dwc need to be reconfigured with params configured during 'hw_params' callback. With this, audio usecase continues from where it got stopped because of 'suspend'.
This is hard to review since there are several different changes in here, one change per commit is muuch more helpful. As is I'm not 100% sure exactly what each bit of the change is intended to do. One example here is that a separate patch splitting pbase and cbase would be really helpful.
Ok, I will split into multiple patches and send again.
--- a/include/sound/designware_i2s.h +++ b/include/sound/designware_i2s.h @@ -44,6 +44,9 @@ struct i2s_platform_data { int channel; u32 snd_fmts; u32 snd_rates;
#define DW_I2S_VENDOR_AMD (1 << 0)
unsigned int quirks;
I would expect to see a set of quirks with the AMD devices selecting those that apply to them rather than just a per vendor define - this is more generic and more maintainable long term. AMD may require different sets of quirks with some future device and systems from other vendors may require some but not all of the quirks that AMD requires.
Agreed. I will send a revised patch.
if (dev->quirks & DW_I2S_VENDOR_AMD) {
comp1 = i2s_read_reg(dev->i2s_pbase, 0x124);
comp2 = i2s_read_reg(dev->i2s_pbase, 0x120);
Please add defines for these registers. Rather than having the quirk code throughout the driver it might be clearer to have the comp1 and comp2 register offsets stored in the driver data so you can just do the actual quirk once at probe time.
Agreed. I will send a revised patch.
Alsa-devel mailing list Alsa-devel@alsa-project.org http://mailman.alsa-project.org/mailman/listinfo/alsa-devel
From: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com
This adds the ACP (Audio CoProcessor) IP driver and wires it up to the amdgpu driver. The ACP block provides the DMA engine for i2s based ALSA driver. This is required for audio on APUs that utilize an i2s codec.
Reviewed-by: Jammy Zhou Jammy.Zhou@amd.com Reviewed-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: Maruthi Bayyavarapu maruthi.bayyavarapu@amd.com Signed-off-by: Chunming Zhou david1.zhou@amd.com Signed-off-by: Alex Deucher alexander.deucher@amd.com ---
v2: integrate i2s/az check patch v3: s/amd_acp/amdgpu_acp/ v4: update copyright notice v5: squash multiple patches, convert to mfd v6: major changes as below : 1. Pass ACP register base to DMA and dw i2s drivers as IORESOURCE_MEM resources. 2. add dw i2s as a new mfd cell. v7: remove dw i2s slave setting from platform data. this is determined in the dw driver directly now.
drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/amd/acp/Kconfig | 10 + drivers/gpu/drm/amd/acp/Makefile | 9 + drivers/gpu/drm/amd/acp/acp_hw.c | 127 +++++++++++++ drivers/gpu/drm/amd/acp/include/acp_gfx_if.h | 49 +++++ drivers/gpu/drm/amd/amdgpu/Makefile | 13 +- drivers/gpu/drm/amd/amdgpu/amdgpu.h | 12 ++ drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 269 +++++++++++++++++++++++++++ drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h | 41 ++++ drivers/gpu/drm/amd/amdgpu/vi.c | 12 ++ drivers/gpu/drm/amd/include/amd_shared.h | 1 + include/linux/mfd/amd_acp.h | 43 +++++ 12 files changed, 587 insertions(+), 1 deletion(-) create mode 100644 drivers/gpu/drm/amd/acp/Kconfig create mode 100644 drivers/gpu/drm/amd/acp/Makefile create mode 100644 drivers/gpu/drm/amd/acp/acp_hw.c create mode 100644 drivers/gpu/drm/amd/acp/include/acp_gfx_if.h create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h create mode 100644 include/linux/mfd/amd_acp.h
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 1a0a8df..330e9fb 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -161,6 +161,8 @@ config DRM_AMDGPU
source "drivers/gpu/drm/amd/amdgpu/Kconfig"
+source "drivers/gpu/drm/amd/acp/Kconfig" + source "drivers/gpu/drm/nouveau/Kconfig"
config DRM_I810 diff --git a/drivers/gpu/drm/amd/acp/Kconfig b/drivers/gpu/drm/amd/acp/Kconfig new file mode 100644 index 0000000..1de4fe7 --- /dev/null +++ b/drivers/gpu/drm/amd/acp/Kconfig @@ -0,0 +1,10 @@ +menu "ACP Configuration" + +config DRM_AMD_ACP + bool "Enable ACP IP support" + default y + depends on MFD_CORE + help + Choose this option to enable ACP IP support for AMD SOCs. + +endmenu diff --git a/drivers/gpu/drm/amd/acp/Makefile b/drivers/gpu/drm/amd/acp/Makefile new file mode 100644 index 0000000..c8c3303 --- /dev/null +++ b/drivers/gpu/drm/amd/acp/Makefile @@ -0,0 +1,9 @@ +# +# Makefile for the ACP, which is a sub-component +# of AMDSOC/AMDGPU drm driver. +# It provides the HW control for ACP related functionalities. + +ccflags-y += -Idrivers/gpu/drm/amd/include/asic_reg/acp +subdir-ccflags-y += -I$(AMDACPPATH)/ -I$(AMDACPPATH)/include + +AMD_ACP_FILES := $(AMDACPPATH)/acp_hw.o diff --git a/drivers/gpu/drm/amd/acp/acp_hw.c b/drivers/gpu/drm/amd/acp/acp_hw.c new file mode 100644 index 0000000..55220c3 --- /dev/null +++ b/drivers/gpu/drm/amd/acp/acp_hw.c @@ -0,0 +1,127 @@ +/* + * Copyright 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/mm.h> +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/errno.h> + +#include "acp_gfx_if.h" + +#define ACP_MODE_I2S 0 +#define ACP_MODE_AZ 1 + +#define VISLANDS30_IV_SRCID_ACP 0x000000a2 +#define mmACP_AZALIA_I2S_SELECT 0x51d4 + +static int irq_set_source(void *private_data, unsigned src_id, unsigned type, + int enabled) +{ + struct acp_irq_prv *idata = private_data; + + if (src_id == VISLANDS30_IV_SRCID_ACP) { + idata->enable_intr(idata->acp_mmio, enabled); + return 0; + } else { + return -1; + } +} + +static int irq_handler(void *private_data, unsigned src_id, + const uint32_t *iv_entry) +{ + struct acp_irq_prv *idata = private_data; + + if (src_id == VISLANDS30_IV_SRCID_ACP) + return idata->irq_handler(idata->dev); + else + return -1; +} + +static void acp_irq_register(struct amd_acp_device *acp_dev, void *iprv) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + cgs_add_irq_source(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 1, + irq_set_source, irq_handler, iprv); +} + +static void acp_irq_get(struct amd_acp_device *acp_dev) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + cgs_irq_get(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 0); +} + +static void acp_irq_put(struct amd_acp_device *acp_dev) +{ + struct amd_acp_private *acp_prv = (struct amd_acp_private *)acp_dev; + + cgs_irq_put(acp_prv->cgs_device, VISLANDS30_IV_SRCID_ACP, 0); +} + +void amd_acp_resume(struct amd_acp_private *acp_private) +{ + +} + + +void amd_acp_suspend(struct amd_acp_private *acp_private) +{ + +} + +int amd_acp_hw_init(void *cgs_device, + unsigned acp_version_major, unsigned acp_version_minor, + struct amd_acp_private **acp_private) +{ + unsigned int acp_mode = ACP_MODE_I2S; + + if ((acp_version_major == 2) && (acp_version_minor == 2)) + acp_mode = cgs_read_register(cgs_device, + mmACP_AZALIA_I2S_SELECT); + + if (acp_mode != ACP_MODE_I2S) + return -ENODEV; + + *acp_private = kzalloc(sizeof(struct amd_acp_private), GFP_KERNEL); + if (*acp_private == NULL) + return -ENOMEM; + + (*acp_private)->cgs_device = cgs_device; + (*acp_private)->acp_version_major = acp_version_major; + (*acp_private)->acp_version_minor = acp_version_minor; + + (*acp_private)->public.irq_register = acp_irq_register; + (*acp_private)->public.irq_get = acp_irq_get; + (*acp_private)->public.irq_put = acp_irq_put; + + return 0; +} + +int amd_acp_hw_fini(struct amd_acp_private *acp_private) +{ + kfree(acp_private); + return 0; +} diff --git a/drivers/gpu/drm/amd/acp/include/acp_gfx_if.h b/drivers/gpu/drm/amd/acp/include/acp_gfx_if.h new file mode 100644 index 0000000..d6988c2 --- /dev/null +++ b/drivers/gpu/drm/amd/acp/include/acp_gfx_if.h @@ -0,0 +1,49 @@ +/* + * Copyright 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. + * +*/ + +#ifndef _ACP_GFX_IF_H +#define _ACP_GFX_IF_H + +#include <linux/types.h> +#include <linux/mfd/amd_acp.h> +#include "cgs_linux.h" +#include "cgs_common.h" + +struct amd_acp_private { + /* The public struture is first, so that pointers can be cast + * between the public and private structure */ + struct amd_acp_device public; + + /* private elements not expose through the bus interface */ + void *cgs_device; + unsigned acp_version_major, acp_version_minor; +}; + +int amd_acp_hw_init(void *cgs_device, + unsigned acp_version_major, unsigned acp_version_minor, + struct amd_acp_private **apriv); +int amd_acp_hw_fini(struct amd_acp_private *apriv); +void amd_acp_suspend(struct amd_acp_private *acp_private); +void amd_acp_resume(struct amd_acp_private *acp_private); + +#endif /* _ACP_GFX_IF_H */ diff --git a/drivers/gpu/drm/amd/amdgpu/Makefile b/drivers/gpu/drm/amd/amdgpu/Makefile index 04c2707..44de879 100644 --- a/drivers/gpu/drm/amd/amdgpu/Makefile +++ b/drivers/gpu/drm/amd/amdgpu/Makefile @@ -5,7 +5,8 @@ ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/amd/include/asic_reg \ -Idrivers/gpu/drm/amd/include \ -Idrivers/gpu/drm/amd/amdgpu \ - -Idrivers/gpu/drm/amd/scheduler + -Idrivers/gpu/drm/amd/scheduler \ + -Idrivers/gpu/drm/amd/acp/include
amdgpu-y := amdgpu_drv.o
@@ -89,6 +90,16 @@ amdgpu-y += \ ../scheduler/sched_fence.o \ amdgpu_sched.o
+# ACP componet +ifneq ($(CONFIG_DRM_AMD_ACP),) +amdgpu-y += amdgpu_acp.o + +AMDACPPATH := ../acp +include drivers/gpu/drm/amd/acp/Makefile + +amdgpu-y += $(AMD_ACP_FILES) +endif + amdgpu-$(CONFIG_COMPAT) += amdgpu_ioc32.o amdgpu-$(CONFIG_VGA_SWITCHEROO) += amdgpu_atpx_handler.o amdgpu-$(CONFIG_ACPI) += amdgpu_acpi.o diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu.h b/drivers/gpu/drm/amd/amdgpu/amdgpu.h index 57b427f..cac1c7c 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu.h @@ -52,6 +52,7 @@ #include "amdgpu_irq.h" #include "amdgpu_ucode.h" #include "amdgpu_gds.h" +#include "amdgpu_acp.h"
#include "gpu_scheduler.h"
@@ -1930,6 +1931,13 @@ void amdgpu_cgs_destroy_device(void *cgs_device);
/* + * CGS + */ +void *amdgpu_cgs_create_device(struct amdgpu_device *adev); +void amdgpu_cgs_destroy_device(void *cgs_device); + + +/* * Core structure, functions and helpers. */ typedef uint32_t (*amdgpu_rreg_t)(struct amdgpu_device*, uint32_t); @@ -1950,6 +1958,10 @@ struct amdgpu_device { struct pci_dev *pdev; struct rw_semaphore exclusive_lock;
+#ifdef CONFIG_DRM_AMD_ACP + struct amdgpu_acp acp; +#endif + /* ASIC */ enum amd_asic_type asic_type; uint32_t family; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c new file mode 100644 index 0000000..5945235 --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c @@ -0,0 +1,269 @@ +/* + * Copyright 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. + * + * Authors: AMD + * + */ + +#include <sound/designware_i2s.h> +#include <sound/pcm.h> + +#include "amdgpu.h" +#include "atom.h" +#include "amdgpu_acp.h" + +#include "acp_gfx_if.h" + +#define ACP_DMA_REGS_END 0x146c0 +#define ACP_I2S_PLAY_REGS_START 0x14840 +#define ACP_I2S_PLAY_REGS_END 0x148b4 +#define ACP_I2S_CAP_REGS_START 0x148b8 +#define ACP_I2S_CAP_REGS_END 0x1496c + +static int acp_early_init(void *handle) +{ + return 0; +} + +static int acp_sw_init(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + adev->acp.parent = adev->dev; + + adev->acp.cgs_device = + amdgpu_cgs_create_device(adev); + if (!adev->acp.cgs_device) + return -EINVAL; + + return 0; +} + +static int acp_sw_fini(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + if (adev->acp.cgs_device) + amdgpu_cgs_destroy_device(adev->acp.cgs_device); + + return 0; +} + +/** + * acp_hw_init - start and test UVD block + * + * @adev: amdgpu_device pointer + * + */ +static int acp_hw_init(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + int r; + uint64_t acp_base; + struct i2s_platform_data *i2s_pdata; + + const struct amdgpu_ip_block_version *ip_version = + amdgpu_get_ip_block(adev, AMD_IP_BLOCK_TYPE_ACP); + + if (!ip_version) + return -EINVAL; + + r = amd_acp_hw_init(adev->acp.cgs_device, + ip_version->major, ip_version->minor, + &adev->acp.private); + /* -ENODEV means board uses AZ rather than ACP */ + if (r == -ENODEV) + return 0; + else if (r) + return r; + + r = cgs_get_pci_resource(adev->acp.cgs_device, CGS_RESOURCE_TYPE_MMIO, + 0x5289, 0, &acp_base); + if (r == -ENODEV) + return 0; + else if (r) + return r; + + adev->acp.acp_cell = kzalloc(sizeof(struct mfd_cell) * 2, GFP_KERNEL); + + if (adev->acp.acp_cell == NULL) + return -ENOMEM; + + adev->acp.acp_res = kzalloc(sizeof(struct resource) * 3, GFP_KERNEL); + + if (adev->acp.acp_res == NULL) { + kfree(adev->acp.acp_cell); + return -ENOMEM; + } + + i2s_pdata = kzalloc(sizeof(struct i2s_platform_data), GFP_KERNEL); + if (i2s_pdata == NULL) { + kfree(adev->acp.acp_res); + kfree(adev->acp.acp_cell); + return -ENOMEM; + } + + i2s_pdata->quirks = DW_I2S_VENDOR_AMD; + i2s_pdata->cap = DWC_I2S_PLAY | DWC_I2S_RECORD; + i2s_pdata->snd_rates = SNDRV_PCM_RATE_8000_96000; + + adev->acp.acp_res[0].name = "acp2x_dma"; + adev->acp.acp_res[0].flags = IORESOURCE_MEM; + adev->acp.acp_res[0].start = acp_base; + adev->acp.acp_res[0].end = acp_base + ACP_DMA_REGS_END; + + adev->acp.acp_res[1].name = "acp2x_dw_i2s_play"; + adev->acp.acp_res[1].flags = IORESOURCE_MEM; + adev->acp.acp_res[1].start = acp_base + ACP_I2S_PLAY_REGS_START; + adev->acp.acp_res[1].end = acp_base + ACP_I2S_PLAY_REGS_END; + + adev->acp.acp_res[2].name = "acp2x_dw_i2s_cap"; + adev->acp.acp_res[2].flags = IORESOURCE_MEM; + adev->acp.acp_res[2].start = acp_base + ACP_I2S_CAP_REGS_START; + adev->acp.acp_res[2].end = acp_base + ACP_I2S_CAP_REGS_END; + + adev->acp.acp_cell[0].name = "acp_audio_dma"; + adev->acp.acp_cell[0].num_resources = 1; + adev->acp.acp_cell[0].resources = &adev->acp.acp_res[0]; + adev->acp.acp_cell[0].platform_data = adev->acp.private; + adev->acp.acp_cell[0].pdata_size = sizeof(struct amd_acp_private); + + adev->acp.acp_cell[1].name = "designware-i2s"; + adev->acp.acp_cell[1].num_resources = 2; + adev->acp.acp_cell[1].resources = &adev->acp.acp_res[1]; + adev->acp.acp_cell[1].platform_data = i2s_pdata; + adev->acp.acp_cell[1].pdata_size = sizeof(struct i2s_platform_data); + + r = mfd_add_hotplug_devices(adev->acp.parent, adev->acp.acp_cell, 2); + + if (r) { + amd_acp_hw_fini(adev->acp.private); + return r; + } + return 0; +} + +/** + * acp_hw_fini - stop the hardware block + * + * @adev: amdgpu_device pointer + * + */ +static int acp_hw_fini(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + if (adev->acp.private) { + amd_acp_hw_fini(adev->acp.private); + mfd_remove_devices(adev->acp.parent); + kfree(adev->acp.acp_res); + kfree(adev->acp.acp_cell); + } + + return 0; +} + +static int acp_suspend(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + if (adev->acp.private) + amd_acp_suspend(adev->acp.private); + + return 0; +} + +static int acp_resume(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + if (adev->acp.private) + amd_acp_resume(adev->acp.private); + + return 0; +} + +static bool acp_is_idle(void *handle) +{ + return true; +} + +static int acp_wait_for_idle(void *handle) +{ + return 0; +} + +static int acp_soft_reset(void *handle) +{ + return 0; +} + +static void acp_print_status(void *handle) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + dev_info(adev->dev, "ACP STATUS\n"); +} + +static int acp_set_clockgating_state(void *handle, + enum amd_clockgating_state state) +{ + return 0; +} + +static int acp_set_powergating_state(void *handle, + enum amd_powergating_state state) +{ + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + /* This doesn't actually powergate the ACP block. + * That's done in the dpm code via the SMC. This + * just re-inits the block as necessary. The actual + * gating still happens in the dpm code. We should + * revisit this when there is a cleaner line between + * the smc and the hw blocks + */ + if (state == AMD_PG_STATE_GATE) { + if (adev->acp.private) + amd_acp_suspend(adev->acp.private); + } else { + if (adev->acp.private) + amd_acp_resume(adev->acp.private); + } + return 0; +} + +const struct amd_ip_funcs acp_ip_funcs = { + .early_init = acp_early_init, + .late_init = NULL, + .sw_init = acp_sw_init, + .sw_fini = acp_sw_fini, + .hw_init = acp_hw_init, + .hw_fini = acp_hw_fini, + .suspend = acp_suspend, + .resume = acp_resume, + .is_idle = acp_is_idle, + .wait_for_idle = acp_wait_for_idle, + .soft_reset = acp_soft_reset, + .print_status = acp_print_status, + .set_clockgating_state = acp_set_clockgating_state, + .set_powergating_state = acp_set_powergating_state, +}; diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h new file mode 100644 index 0000000..24952ed --- /dev/null +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h @@ -0,0 +1,41 @@ +/* + * Copyright 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. + * + * Authors: AMD + * + */ + +#ifndef __AMDGPU_ACP_H__ +#define __AMDGPU_ACP_H__ + +#include <linux/mfd/core.h> + +struct amdgpu_acp { + struct device *parent; + void *cgs_device; + struct amd_acp_private *private; + struct mfd_cell *acp_cell; + struct resource *acp_res; +}; + +extern const struct amd_ip_funcs acp_ip_funcs; + +#endif /* __AMDGPU_ACP_H__ */ diff --git a/drivers/gpu/drm/amd/amdgpu/vi.c b/drivers/gpu/drm/amd/amdgpu/vi.c index b55ceb1..ffd8727 100644 --- a/drivers/gpu/drm/amd/amdgpu/vi.c +++ b/drivers/gpu/drm/amd/amdgpu/vi.c @@ -71,6 +71,9 @@ #include "uvd_v5_0.h" #include "uvd_v6_0.h" #include "vce_v3_0.h" +#if defined(CONFIG_DRM_AMD_ACP) +#include "amdgpu_acp.h" +#endif
/* * Indirect registers accessor @@ -1298,6 +1301,15 @@ static const struct amdgpu_ip_block_version cz_ip_blocks[] = .rev = 0, .funcs = &vce_v3_0_ip_funcs, }, +#if defined(CONFIG_DRM_AMD_ACP) + { + .type = AMD_IP_BLOCK_TYPE_ACP, + .major = 2, + .minor = 2, + .rev = 0, + .funcs = &acp_ip_funcs, + }, +#endif };
int vi_set_ip_blocks(struct amdgpu_device *adev) diff --git a/drivers/gpu/drm/amd/include/amd_shared.h b/drivers/gpu/drm/amd/include/amd_shared.h index 68a8eaa..54bf96a 100644 --- a/drivers/gpu/drm/amd/include/amd_shared.h +++ b/drivers/gpu/drm/amd/include/amd_shared.h @@ -72,6 +72,7 @@ enum amd_ip_block_type { AMD_IP_BLOCK_TYPE_SDMA, AMD_IP_BLOCK_TYPE_UVD, AMD_IP_BLOCK_TYPE_VCE, + AMD_IP_BLOCK_TYPE_ACP, };
enum amd_clockgating_state { diff --git a/include/linux/mfd/amd_acp.h b/include/linux/mfd/amd_acp.h new file mode 100644 index 0000000..8290870 --- /dev/null +++ b/include/linux/mfd/amd_acp.h @@ -0,0 +1,43 @@ +/* + * Copyright 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. + * +*/ + +#ifndef _AMD_ACP_H +#define _AMD_ACP_H + +#include <linux/types.h> + +struct acp_irq_prv { + struct device *dev; + void __iomem *acp_mmio; + int (*irq_handler)(struct device *dev); + void (*enable_intr)(void __iomem *acp_mmio, int enable); +}; + +/* Public interface of ACP device */ +struct amd_acp_device { + void (*irq_register)(struct amd_acp_device *acp_dev, void *iprv); + void (*irq_get)(struct amd_acp_device *acp_dev); + void (*irq_put)(struct amd_acp_device *acp_dev); +}; + +#endif /* _AMD_ACP_H */
participants (3)
-
Alex Deucher
-
Mark Brown
-
maruthi srinivas