diff options
Diffstat (limited to 'common/recipes-kernel/linux/files/0258-drm-amd-add-pm-domain-for-ACP-IP-sub-blocks.patch')
-rw-r--r-- | common/recipes-kernel/linux/files/0258-drm-amd-add-pm-domain-for-ACP-IP-sub-blocks.patch | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/common/recipes-kernel/linux/files/0258-drm-amd-add-pm-domain-for-ACP-IP-sub-blocks.patch b/common/recipes-kernel/linux/files/0258-drm-amd-add-pm-domain-for-ACP-IP-sub-blocks.patch new file mode 100644 index 00000000..2c43d4be --- /dev/null +++ b/common/recipes-kernel/linux/files/0258-drm-amd-add-pm-domain-for-ACP-IP-sub-blocks.patch @@ -0,0 +1,311 @@ +From c4179391cc0a3321b45cda7fcc0431d71421e58d Mon Sep 17 00:00:00 2001 +From: Maruthi Srinivas Bayyavarapu <Maruthi.Bayyavarapu@amd.com> +Date: Mon, 23 Nov 2015 21:07:30 +0530 +Subject: [PATCH 0258/1110] 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 <maruthi.bayyavarapu@amd.com> +Reviewed-by: Alex Deucher <alexander.deucher@amd.com> +Signed-off-by: Alex Deucher <alexander.deucher@amd.com> +--- + 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(-) + +diff --git a/drivers/gpu/drm/amd/acp/Kconfig b/drivers/gpu/drm/amd/acp/Kconfig +index 28b5e70..2b07813 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 71f26e9..9f8cfaa 100644 +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_acp.c +@@ -24,6 +24,7 @@ + */ + + #include <linux/irqdomain.h> ++#include <linux/pm_domain.h> + #include <linux/platform_device.h> + #include <sound/designware_i2s.h> + #include <sound/pcm.h> +@@ -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 24952ed..f6e32a6 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; +-- +2.7.4 + |