aboutsummaryrefslogtreecommitdiffstats
path: root/common/recipes-kernel/linux/files/0258-drm-amd-add-pm-domain-for-ACP-IP-sub-blocks.patch
diff options
context:
space:
mode:
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.patch311
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
+