|
14 | 14 | #include <linux/slab.h> |
15 | 15 | #include <linux/soundwire/sdw.h> |
16 | 16 | #include <linux/soundwire/sdw_registers.h> |
| 17 | +#include <linux/pm_runtime.h> |
17 | 18 | #include <linux/wait.h> |
18 | 19 | #include <sound/pcm_params.h> |
19 | 20 | #include <sound/soc.h> |
@@ -133,6 +134,12 @@ static void amd_disable_sdw_interrupts(struct amd_sdw_manager *amd_manager) |
133 | 134 | writel(0x00, amd_manager->mmio + ACP_SW_ERROR_INTR_MASK); |
134 | 135 | } |
135 | 136 |
|
| 137 | +static int amd_deinit_sdw_manager(struct amd_sdw_manager *amd_manager) |
| 138 | +{ |
| 139 | + amd_disable_sdw_interrupts(amd_manager); |
| 140 | + return amd_disable_sdw_manager(amd_manager); |
| 141 | +} |
| 142 | + |
136 | 143 | static void amd_sdw_set_frameshape(struct amd_sdw_manager *amd_manager) |
137 | 144 | { |
138 | 145 | u32 frame_size; |
@@ -866,6 +873,12 @@ static void amd_sdw_probe_work(struct work_struct *work) |
866 | 873 | return; |
867 | 874 | amd_sdw_set_frameshape(amd_manager); |
868 | 875 | } |
| 876 | + /* Enable runtime PM */ |
| 877 | + pm_runtime_set_autosuspend_delay(amd_manager->dev, AMD_SDW_MASTER_SUSPEND_DELAY_MS); |
| 878 | + pm_runtime_use_autosuspend(amd_manager->dev); |
| 879 | + pm_runtime_mark_last_busy(amd_manager->dev); |
| 880 | + pm_runtime_set_active(amd_manager->dev); |
| 881 | + pm_runtime_enable(amd_manager->dev); |
869 | 882 | } |
870 | 883 |
|
871 | 884 | static int amd_sdw_manager_probe(struct platform_device *pdev) |
@@ -953,17 +966,144 @@ static int amd_sdw_manager_remove(struct platform_device *pdev) |
953 | 966 | { |
954 | 967 | struct amd_sdw_manager *amd_manager = dev_get_drvdata(&pdev->dev); |
955 | 968 |
|
| 969 | + pm_runtime_disable(&pdev->dev); |
956 | 970 | cancel_work_sync(&amd_manager->probe_work); |
957 | 971 | amd_disable_sdw_interrupts(amd_manager); |
958 | 972 | sdw_bus_master_delete(&amd_manager->bus); |
959 | 973 | return amd_disable_sdw_manager(amd_manager); |
960 | 974 | } |
961 | 975 |
|
| 976 | +static int amd_sdw_clock_stop(struct amd_sdw_manager *amd_manager) |
| 977 | +{ |
| 978 | + u32 val; |
| 979 | + int ret; |
| 980 | + |
| 981 | + ret = sdw_bus_prep_clk_stop(&amd_manager->bus); |
| 982 | + if (ret < 0 && ret != -ENODATA) { |
| 983 | + dev_err(amd_manager->dev, "prepare clock stop failed %d", ret); |
| 984 | + return 0; |
| 985 | + } |
| 986 | + ret = sdw_bus_clk_stop(&amd_manager->bus); |
| 987 | + if (ret < 0 && ret != -ENODATA) { |
| 988 | + dev_err(amd_manager->dev, "bus clock stop failed %d", ret); |
| 989 | + return 0; |
| 990 | + } |
| 991 | + |
| 992 | + ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL, val, |
| 993 | + (val & AMD_SDW_CLK_STOP_DONE), ACP_DELAY_US, AMD_SDW_TIMEOUT); |
| 994 | + if (ret) { |
| 995 | + dev_err(amd_manager->dev, "SDW%x clock stop failed\n", amd_manager->instance); |
| 996 | + return 0; |
| 997 | + } |
| 998 | + |
| 999 | + amd_manager->clk_stopped = true; |
| 1000 | + if (amd_manager->wake_en_mask) |
| 1001 | + writel(0x01, amd_manager->acp_mmio + ACP_SW_WAKE_EN(amd_manager->instance)); |
| 1002 | + |
| 1003 | + dev_dbg(amd_manager->dev, "SDW%x clock stop successful\n", amd_manager->instance); |
| 1004 | + return 0; |
| 1005 | +} |
| 1006 | + |
| 1007 | +static int amd_sdw_clock_stop_exit(struct amd_sdw_manager *amd_manager) |
| 1008 | +{ |
| 1009 | + int ret; |
| 1010 | + u32 val; |
| 1011 | + |
| 1012 | + if (amd_manager->clk_stopped) { |
| 1013 | + val = readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); |
| 1014 | + val |= AMD_SDW_CLK_RESUME_REQ; |
| 1015 | + writel(val, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); |
| 1016 | + ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL, val, |
| 1017 | + (val & AMD_SDW_CLK_RESUME_DONE), ACP_DELAY_US, |
| 1018 | + AMD_SDW_TIMEOUT); |
| 1019 | + if (val & AMD_SDW_CLK_RESUME_DONE) { |
| 1020 | + writel(0, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); |
| 1021 | + ret = sdw_bus_exit_clk_stop(&amd_manager->bus); |
| 1022 | + if (ret < 0) |
| 1023 | + dev_err(amd_manager->dev, "bus failed to exit clock stop %d\n", |
| 1024 | + ret); |
| 1025 | + amd_manager->clk_stopped = false; |
| 1026 | + } |
| 1027 | + } |
| 1028 | + if (amd_manager->clk_stopped) { |
| 1029 | + dev_err(amd_manager->dev, "SDW%x clock stop exit failed\n", amd_manager->instance); |
| 1030 | + return 0; |
| 1031 | + } |
| 1032 | + dev_dbg(amd_manager->dev, "SDW%x clock stop exit successful\n", amd_manager->instance); |
| 1033 | + return 0; |
| 1034 | +} |
| 1035 | + |
| 1036 | +static int __maybe_unused amd_suspend_runtime(struct device *dev) |
| 1037 | +{ |
| 1038 | + struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev); |
| 1039 | + struct sdw_bus *bus = &amd_manager->bus; |
| 1040 | + int ret; |
| 1041 | + |
| 1042 | + if (bus->prop.hw_disabled) { |
| 1043 | + dev_dbg(bus->dev, "SoundWire manager %d is disabled,\n", |
| 1044 | + bus->link_id); |
| 1045 | + return 0; |
| 1046 | + } |
| 1047 | + if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { |
| 1048 | + return amd_sdw_clock_stop(amd_manager); |
| 1049 | + } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { |
| 1050 | + ret = amd_sdw_clock_stop(amd_manager); |
| 1051 | + if (ret) |
| 1052 | + return ret; |
| 1053 | + return amd_deinit_sdw_manager(amd_manager); |
| 1054 | + } |
| 1055 | + return 0; |
| 1056 | +} |
| 1057 | + |
| 1058 | +static int __maybe_unused amd_resume_runtime(struct device *dev) |
| 1059 | +{ |
| 1060 | + struct amd_sdw_manager *amd_manager = dev_get_drvdata(dev); |
| 1061 | + struct sdw_bus *bus = &amd_manager->bus; |
| 1062 | + int ret; |
| 1063 | + u32 val; |
| 1064 | + |
| 1065 | + if (bus->prop.hw_disabled) { |
| 1066 | + dev_dbg(bus->dev, "SoundWire manager %d is disabled, ignoring\n", |
| 1067 | + bus->link_id); |
| 1068 | + return 0; |
| 1069 | + } |
| 1070 | + |
| 1071 | + if (amd_manager->power_mode_mask & AMD_SDW_CLK_STOP_MODE) { |
| 1072 | + return amd_sdw_clock_stop_exit(amd_manager); |
| 1073 | + } else if (amd_manager->power_mode_mask & AMD_SDW_POWER_OFF_MODE) { |
| 1074 | + val = readl(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); |
| 1075 | + if (val) { |
| 1076 | + val |= AMD_SDW_CLK_RESUME_REQ; |
| 1077 | + writel(val, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); |
| 1078 | + ret = readl_poll_timeout(amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL, val, |
| 1079 | + (val & AMD_SDW_CLK_RESUME_DONE), ACP_DELAY_US, |
| 1080 | + AMD_SDW_TIMEOUT); |
| 1081 | + if (val & AMD_SDW_CLK_RESUME_DONE) { |
| 1082 | + writel(0, amd_manager->mmio + ACP_SW_CLK_RESUME_CTRL); |
| 1083 | + amd_manager->clk_stopped = false; |
| 1084 | + } |
| 1085 | + } |
| 1086 | + sdw_clear_slave_status(bus, SDW_UNATTACH_REQUEST_MASTER_RESET); |
| 1087 | + amd_init_sdw_manager(amd_manager); |
| 1088 | + amd_enable_sdw_interrupts(amd_manager); |
| 1089 | + ret = amd_enable_sdw_manager(amd_manager); |
| 1090 | + if (ret) |
| 1091 | + return ret; |
| 1092 | + amd_sdw_set_frameshape(amd_manager); |
| 1093 | + } |
| 1094 | + return 0; |
| 1095 | +} |
| 1096 | + |
| 1097 | +static const struct dev_pm_ops amd_pm = { |
| 1098 | + SET_RUNTIME_PM_OPS(amd_suspend_runtime, amd_resume_runtime, NULL) |
| 1099 | +}; |
| 1100 | + |
962 | 1101 | static struct platform_driver amd_sdw_driver = { |
963 | 1102 | .probe = &amd_sdw_manager_probe, |
964 | 1103 | .remove = &amd_sdw_manager_remove, |
965 | 1104 | .driver = { |
966 | 1105 | .name = "amd_sdw_manager", |
| 1106 | + .pm = &amd_pm, |
967 | 1107 | } |
968 | 1108 | }; |
969 | 1109 | module_platform_driver(amd_sdw_driver); |
|
0 commit comments