From a8fe58cec351c25e09c393bf46117c0c47b5a17c Mon Sep 17 00:00:00 2001 From: Maruthi Bayyavarapu Date: Tue, 22 Sep 2015 17:05:20 -0400 Subject: drm/amd: add ACP driver support 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 Reviewed-by: Maruthi Bayyavarapu Reviewed-by: Alex Deucher Reviewed-by: Murali Krishna Vemuri Signed-off-by: Maruthi Bayyavarapu Signed-off-by: Chunming Zhou Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h | 41 +++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h (limited to 'drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h') 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 000000000000..24952ed8209e --- /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 + +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__ */ -- cgit v1.2.3 From 25030321ba2860c56651429a4f28d94a6483d952 Mon Sep 17 00:00:00 2001 From: Maruthi Srinivas Bayyavarapu Date: Mon, 23 Nov 2015 21:07:30 +0530 Subject: drm/amd: add pm domain for ACP IP sub blocks ACP IP have internal DMA controller, DW I2S controller and DSPs as separate power tiles. DMA and I2S devices are added to generic pm domain, so that entire IP can be powered off/on at appropriate times. Unused DSPs are made to be powered off though they are powered on during ACP pm domain power on sequence. Signed-off-by: Maruthi Bayyavarapu Reviewed-by: Alex Deucher Signed-off-by: Alex Deucher --- drivers/gpu/drm/amd/acp/Kconfig | 1 + drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c | 206 +++++++++++++++++++++++++++++++- drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h | 1 + 3 files changed, 207 insertions(+), 1 deletion(-) (limited to 'drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h') diff --git a/drivers/gpu/drm/amd/acp/Kconfig b/drivers/gpu/drm/amd/acp/Kconfig index 28b5e70b502f..2b07813bceed 100644 --- a/drivers/gpu/drm/amd/acp/Kconfig +++ b/drivers/gpu/drm/amd/acp/Kconfig @@ -4,6 +4,7 @@ config DRM_AMD_ACP bool "Enable ACP IP support" default y select MFD_CORE + select PM_GENERIC_DOMAINS if PM help Choose this option to enable ACP IP support for AMD SOCs. diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c index 71f26e93ef58..9f8cfaab3004 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -102,6 +103,155 @@ static int acp_sw_fini(void *handle) return 0; } +/* power off a tile/block within ACP */ +static int acp_suspend_tile(void *cgs_dev, int tile) +{ + u32 val = 0; + u32 count = 0; + + if ((tile < ACP_TILE_P1) || (tile > ACP_TILE_DSP2)) { + pr_err("Invalid ACP tile : %d to suspend\n", tile); + return -1; + } + + val = cgs_read_register(cgs_dev, mmACP_PGFSM_READ_REG_0 + tile); + val &= ACP_TILE_ON_MASK; + + if (val == 0x0) { + val = cgs_read_register(cgs_dev, mmACP_PGFSM_RETAIN_REG); + val = val | (1 << tile); + cgs_write_register(cgs_dev, mmACP_PGFSM_RETAIN_REG, val); + cgs_write_register(cgs_dev, mmACP_PGFSM_CONFIG_REG, + 0x500 + tile); + + count = ACP_TIMEOUT_LOOP; + while (true) { + val = cgs_read_register(cgs_dev, mmACP_PGFSM_READ_REG_0 + + tile); + val = val & ACP_TILE_ON_MASK; + if (val == ACP_TILE_OFF_MASK) + break; + if (--count == 0) { + pr_err("Timeout reading ACP PGFSM status\n"); + return -ETIMEDOUT; + } + udelay(100); + } + + val = cgs_read_register(cgs_dev, mmACP_PGFSM_RETAIN_REG); + + val |= ACP_TILE_OFF_RETAIN_REG_MASK; + cgs_write_register(cgs_dev, mmACP_PGFSM_RETAIN_REG, val); + } + return 0; +} + +/* power on a tile/block within ACP */ +static int acp_resume_tile(void *cgs_dev, int tile) +{ + u32 val = 0; + u32 count = 0; + + if ((tile < ACP_TILE_P1) || (tile > ACP_TILE_DSP2)) { + pr_err("Invalid ACP tile to resume\n"); + return -1; + } + + val = cgs_read_register(cgs_dev, mmACP_PGFSM_READ_REG_0 + tile); + val = val & ACP_TILE_ON_MASK; + + if (val != 0x0) { + cgs_write_register(cgs_dev, mmACP_PGFSM_CONFIG_REG, + 0x600 + tile); + count = ACP_TIMEOUT_LOOP; + while (true) { + val = cgs_read_register(cgs_dev, mmACP_PGFSM_READ_REG_0 + + tile); + val = val & ACP_TILE_ON_MASK; + if (val == 0x0) + break; + if (--count == 0) { + pr_err("Timeout reading ACP PGFSM status\n"); + return -ETIMEDOUT; + } + udelay(100); + } + val = cgs_read_register(cgs_dev, 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(cgs_dev, mmACP_PGFSM_RETAIN_REG, val); + } + return 0; +} + +struct acp_pm_domain { + void *cgs_dev; + struct generic_pm_domain gpd; +}; + +static int acp_poweroff(struct generic_pm_domain *genpd) +{ + int i, ret; + struct acp_pm_domain *apd; + + apd = container_of(genpd, struct acp_pm_domain, gpd); + if (apd != NULL) { + /* Donot return abruptly if any of power tile fails to suspend. + * Log it and continue powering off other tile + */ + for (i = 4; i >= 0 ; i--) { + ret = acp_suspend_tile(apd->cgs_dev, ACP_TILE_P1 + i); + if (ret) + pr_err("ACP tile %d tile suspend failed\n", i); + } + } + return 0; +} + +static int acp_poweron(struct generic_pm_domain *genpd) +{ + int i, ret; + struct acp_pm_domain *apd; + + apd = container_of(genpd, struct acp_pm_domain, gpd); + if (apd != NULL) { + for (i = 0; i < 2; i++) { + ret = acp_resume_tile(apd->cgs_dev, ACP_TILE_P1 + i); + if (ret) { + pr_err("ACP tile %d resume failed\n", i); + break; + } + } + + /* Disable DSPs which are not going to be used */ + for (i = 0; i < 3; i++) { + ret = acp_suspend_tile(apd->cgs_dev, ACP_TILE_DSP0 + i); + /* Continue suspending other DSP, even if one fails */ + if (ret) + pr_err("ACP DSP %d suspend failed\n", i); + } + } + return 0; +} + +static struct device *get_mfd_cell_dev(const char *device_name, int r) +{ + char auto_dev_name[25]; + char buf[8]; + struct device *dev; + + sprintf(buf, ".%d.auto", r); + strcpy(auto_dev_name, device_name); + strcat(auto_dev_name, buf); + dev = bus_find_device_by_name(&platform_bus_type, NULL, auto_dev_name); + dev_info(dev, "device %s added to pm domain\n", auto_dev_name); + + return dev; +} + /** * acp_hw_init - start and test ACP block * @@ -110,8 +260,9 @@ static int acp_sw_fini(void *handle) */ static int acp_hw_init(void *handle) { - int r; + int r, i; uint64_t acp_base; + struct device *dev; struct i2s_platform_data *i2s_pdata; struct amdgpu_device *adev = (struct amdgpu_device *)handle; @@ -137,6 +288,19 @@ static int acp_hw_init(void *handle) else if (r) return r; + adev->acp.acp_genpd = kzalloc(sizeof(struct acp_pm_domain), GFP_KERNEL); + if (adev->acp.acp_genpd == NULL) + return -ENOMEM; + + adev->acp.acp_genpd->gpd.name = "ACP_AUDIO"; + adev->acp.acp_genpd->gpd.power_off = acp_poweroff; + adev->acp.acp_genpd->gpd.power_on = acp_poweron; + + + adev->acp.acp_genpd->cgs_dev = adev->acp.cgs_device; + + pm_genpd_init(&adev->acp.acp_genpd->gpd, NULL, false); + adev->acp.acp_cell = kzalloc(sizeof(struct mfd_cell) * ACP_DEVS, GFP_KERNEL); @@ -211,6 +375,15 @@ static int acp_hw_init(void *handle) if (r) return r; + for (i = 0; i < ACP_DEVS ; i++) { + dev = get_mfd_cell_dev(adev->acp.acp_cell[i].name, i); + r = pm_genpd_add_device(&adev->acp.acp_genpd->gpd, dev); + if (r) { + dev_err(dev, "Failed to add dev to genpd\n"); + return r; + } + } + return 0; } @@ -222,10 +395,22 @@ static int acp_hw_init(void *handle) */ static int acp_hw_fini(void *handle) { + int i, ret; + struct device *dev; + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + for (i = 0; i < ACP_DEVS ; i++) { + dev = get_mfd_cell_dev(adev->acp.acp_cell[i].name, i); + ret = pm_genpd_remove_device(&adev->acp.acp_genpd->gpd, dev); + /* If removal fails, dont giveup and try rest */ + if (ret) + dev_err(dev, "remove dev from genpd failed\n"); + } + mfd_remove_devices(adev->acp.parent); kfree(adev->acp.acp_res); + kfree(adev->acp.acp_genpd); kfree(adev->acp.acp_cell); return 0; @@ -238,6 +423,25 @@ static int acp_suspend(void *handle) static int acp_resume(void *handle) { + int i, ret; + struct acp_pm_domain *apd; + struct amdgpu_device *adev = (struct amdgpu_device *)handle; + + /* SMU block will power on ACP irrespective of ACP runtime status. + * Power off explicitly based on genpd ACP runtime status so that ACP + * hw and ACP-genpd status are in sync. + * 'suspend_power_off' represents "Power status before system suspend" + */ + if (adev->acp.acp_genpd->gpd.suspend_power_off == true) { + apd = container_of(&adev->acp.acp_genpd->gpd, + struct acp_pm_domain, gpd); + + for (i = 4; i >= 0 ; i--) { + ret = acp_suspend_tile(apd->cgs_dev, ACP_TILE_P1 + i); + if (ret) + pr_err("ACP tile %d tile suspend failed\n", i); + } + } return 0; } diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h index 24952ed8209e..f6e32a639107 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.h @@ -34,6 +34,7 @@ struct amdgpu_acp { struct amd_acp_private *private; struct mfd_cell *acp_cell; struct resource *acp_res; + struct acp_pm_domain *acp_genpd; }; extern const struct amd_ip_funcs acp_ip_funcs; -- cgit v1.2.3