1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Surface Platform Profile / Performance Mode driver for Surface System 4 * Aggregator Module (thermal subsystem). 5 * 6 * Copyright (C) 2021-2022 Maximilian Luz <luzmaximilian@gmail.com> 7 */ 8 9 #include <asm/unaligned.h> 10 #include <linux/kernel.h> 11 #include <linux/module.h> 12 #include <linux/platform_profile.h> 13 #include <linux/types.h> 14 15 #include <linux/surface_aggregator/device.h> 16 17 enum ssam_tmp_profile { 18 SSAM_TMP_PROFILE_NORMAL = 1, 19 SSAM_TMP_PROFILE_BATTERY_SAVER = 2, 20 SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3, 21 SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4, 22 }; 23 24 struct ssam_tmp_profile_info { 25 __le32 profile; 26 __le16 unknown1; 27 __le16 unknown2; 28 } __packed; 29 30 struct ssam_tmp_profile_device { 31 struct ssam_device *sdev; 32 struct platform_profile_handler handler; 33 }; 34 35 SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, { 36 .target_category = SSAM_SSH_TC_TMP, 37 .command_id = 0x02, 38 }); 39 40 SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, { 41 .target_category = SSAM_SSH_TC_TMP, 42 .command_id = 0x03, 43 }); 44 45 static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p) 46 { 47 struct ssam_tmp_profile_info info; 48 int status; 49 50 status = ssam_retry(__ssam_tmp_profile_get, sdev, &info); 51 if (status < 0) 52 return status; 53 54 *p = le32_to_cpu(info.profile); 55 return 0; 56 } 57 58 static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p) 59 { 60 __le32 profile_le = cpu_to_le32(p); 61 62 return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le); 63 } 64 65 static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p) 66 { 67 switch (p) { 68 case SSAM_TMP_PROFILE_NORMAL: 69 return PLATFORM_PROFILE_BALANCED; 70 71 case SSAM_TMP_PROFILE_BATTERY_SAVER: 72 return PLATFORM_PROFILE_LOW_POWER; 73 74 case SSAM_TMP_PROFILE_BETTER_PERFORMANCE: 75 return PLATFORM_PROFILE_BALANCED_PERFORMANCE; 76 77 case SSAM_TMP_PROFILE_BEST_PERFORMANCE: 78 return PLATFORM_PROFILE_PERFORMANCE; 79 80 default: 81 dev_err(&sdev->dev, "invalid performance profile: %d", p); 82 return -EINVAL; 83 } 84 } 85 86 static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p) 87 { 88 switch (p) { 89 case PLATFORM_PROFILE_LOW_POWER: 90 return SSAM_TMP_PROFILE_BATTERY_SAVER; 91 92 case PLATFORM_PROFILE_BALANCED: 93 return SSAM_TMP_PROFILE_NORMAL; 94 95 case PLATFORM_PROFILE_BALANCED_PERFORMANCE: 96 return SSAM_TMP_PROFILE_BETTER_PERFORMANCE; 97 98 case PLATFORM_PROFILE_PERFORMANCE: 99 return SSAM_TMP_PROFILE_BEST_PERFORMANCE; 100 101 default: 102 /* This should have already been caught by platform_profile_store(). */ 103 WARN(true, "unsupported platform profile"); 104 return -EOPNOTSUPP; 105 } 106 } 107 108 static int ssam_platform_profile_get(struct platform_profile_handler *pprof, 109 enum platform_profile_option *profile) 110 { 111 struct ssam_tmp_profile_device *tpd; 112 enum ssam_tmp_profile tp; 113 int status; 114 115 tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); 116 117 status = ssam_tmp_profile_get(tpd->sdev, &tp); 118 if (status) 119 return status; 120 121 status = convert_ssam_to_profile(tpd->sdev, tp); 122 if (status < 0) 123 return status; 124 125 *profile = status; 126 return 0; 127 } 128 129 static int ssam_platform_profile_set(struct platform_profile_handler *pprof, 130 enum platform_profile_option profile) 131 { 132 struct ssam_tmp_profile_device *tpd; 133 int tp; 134 135 tpd = container_of(pprof, struct ssam_tmp_profile_device, handler); 136 137 tp = convert_profile_to_ssam(tpd->sdev, profile); 138 if (tp < 0) 139 return tp; 140 141 return ssam_tmp_profile_set(tpd->sdev, tp); 142 } 143 144 static int surface_platform_profile_probe(struct ssam_device *sdev) 145 { 146 struct ssam_tmp_profile_device *tpd; 147 148 tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL); 149 if (!tpd) 150 return -ENOMEM; 151 152 tpd->sdev = sdev; 153 154 tpd->handler.profile_get = ssam_platform_profile_get; 155 tpd->handler.profile_set = ssam_platform_profile_set; 156 157 set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices); 158 set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices); 159 set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices); 160 set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices); 161 162 platform_profile_register(&tpd->handler); 163 return 0; 164 } 165 166 static void surface_platform_profile_remove(struct ssam_device *sdev) 167 { 168 platform_profile_remove(); 169 } 170 171 static const struct ssam_device_id ssam_platform_profile_match[] = { 172 { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, 173 { }, 174 }; 175 MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match); 176 177 static struct ssam_device_driver surface_platform_profile = { 178 .probe = surface_platform_profile_probe, 179 .remove = surface_platform_profile_remove, 180 .match_table = ssam_platform_profile_match, 181 .driver = { 182 .name = "surface_platform_profile", 183 .probe_type = PROBE_PREFER_ASYNCHRONOUS, 184 }, 185 }; 186 module_ssam_device_driver(surface_platform_profile); 187 188 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 189 MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module"); 190 MODULE_LICENSE("GPL"); 191