| 1 | |
| 2 | |
| 3 | * Surface Platform Profile / Performance Mode driver for Surface System
|
| 4 | * Aggregator Module (thermal and fan subsystem).
|
| 5 | *
|
| 6 | * Copyright (C) 2021-2022 Maximilian Luz <[email protected]>
|
| 7 |
|
| 8 |
|
| 9 | #include <linux/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 | |
| 18 | enum ssam_tmp_profile {
|
| 19 | SSAM_TMP_PROFILE_NORMAL = 1,
|
| 20 | SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
|
| 21 | SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
|
| 22 | SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
|
| 23 | };
|
| 24 |
|
| 25 | |
| 26 | |
| 27 | |
| 28 | enum ssam_fan_profile {
|
| 29 | SSAM_FAN_PROFILE_NORMAL = 2,
|
| 30 | SSAM_FAN_PROFILE_BATTERY_SAVER = 1,
|
| 31 | SSAM_FAN_PROFILE_BETTER_PERFORMANCE = 3,
|
| 32 | SSAM_FAN_PROFILE_BEST_PERFORMANCE = 4,
|
| 33 | };
|
| 34 |
|
| 35 | struct ssam_tmp_profile_info {
|
| 36 | __le32 profile;
|
| 37 | __le16 unknown1;
|
| 38 | __le16 unknown2;
|
| 39 | } __packed;
|
| 40 |
|
| 41 | struct ssam_platform_profile_device {
|
| 42 | struct ssam_device *sdev;
|
| 43 | struct device *ppdev;
|
| 44 | bool has_fan;
|
| 45 | };
|
| 46 |
|
| 47 | SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
|
| 48 | .target_category = SSAM_SSH_TC_TMP,
|
| 49 | .command_id = 0x02,
|
| 50 | });
|
| 51 |
|
| 52 | SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
|
| 53 | .target_category = SSAM_SSH_TC_TMP,
|
| 54 | .command_id = 0x03,
|
| 55 | });
|
| 56 |
|
| 57 | SSAM_DEFINE_SYNC_REQUEST_W(__ssam_fan_profile_set, u8, {
|
| 58 | .target_category = SSAM_SSH_TC_FAN,
|
| 59 | .target_id = SSAM_SSH_TID_SAM,
|
| 60 | .command_id = 0x0e,
|
| 61 | .instance_id = 0x01,
|
| 62 | });
|
| 63 |
|
| 64 | static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
|
| 65 | {
|
| 66 | struct ssam_tmp_profile_info info;
|
| 67 | int status;
|
| 68 |
|
| 69 | status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
|
| 70 | if (status < 0)
|
| 71 | return status;
|
| 72 |
|
| 73 | *p = le32_to_cpu(info.profile);
|
| 74 | return 0;
|
| 75 | }
|
| 76 |
|
| 77 | static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
|
| 78 | {
|
| 79 | const __le32 profile_le = cpu_to_le32(p);
|
| 80 |
|
| 81 | return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
|
| 82 | }
|
| 83 |
|
| 84 | static int ssam_fan_profile_set(struct ssam_device *sdev, enum ssam_fan_profile p)
|
| 85 | {
|
| 86 | const u8 profile = p;
|
| 87 |
|
| 88 | return ssam_retry(__ssam_fan_profile_set, sdev->ctrl, &profile);
|
| 89 | }
|
| 90 |
|
| 91 | static int convert_ssam_tmp_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
|
| 92 | {
|
| 93 | switch (p) {
|
| 94 | case SSAM_TMP_PROFILE_NORMAL:
|
| 95 | return PLATFORM_PROFILE_BALANCED;
|
| 96 |
|
| 97 | case SSAM_TMP_PROFILE_BATTERY_SAVER:
|
| 98 | return PLATFORM_PROFILE_LOW_POWER;
|
| 99 |
|
| 100 | case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
|
| 101 | return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
|
| 102 |
|
| 103 | case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
|
| 104 | return PLATFORM_PROFILE_PERFORMANCE;
|
| 105 |
|
| 106 | default:
|
| 107 | dev_err(&sdev->dev, "invalid performance profile: %d", p);
|
| 108 | return -EINVAL;
|
| 109 | }
|
| 110 | }
|
| 111 |
|
| 112 |
|
| 113 | static int convert_profile_to_ssam_tmp(struct ssam_device *sdev, enum platform_profile_option p)
|
| 114 | {
|
| 115 | switch (p) {
|
| 116 | case PLATFORM_PROFILE_LOW_POWER:
|
| 117 | return SSAM_TMP_PROFILE_BATTERY_SAVER;
|
| 118 |
|
| 119 | case PLATFORM_PROFILE_BALANCED:
|
| 120 | return SSAM_TMP_PROFILE_NORMAL;
|
| 121 |
|
| 122 | case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
|
| 123 | return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
|
| 124 |
|
| 125 | case PLATFORM_PROFILE_PERFORMANCE:
|
| 126 | return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
|
| 127 |
|
| 128 | default:
|
| 129 |
|
| 130 | WARN(true, "unsupported platform profile");
|
| 131 | return -EOPNOTSUPP;
|
| 132 | }
|
| 133 | }
|
| 134 |
|
| 135 | static int convert_profile_to_ssam_fan(struct ssam_device *sdev, enum platform_profile_option p)
|
| 136 | {
|
| 137 | switch (p) {
|
| 138 | case PLATFORM_PROFILE_LOW_POWER:
|
| 139 | return SSAM_FAN_PROFILE_BATTERY_SAVER;
|
| 140 |
|
| 141 | case PLATFORM_PROFILE_BALANCED:
|
| 142 | return SSAM_FAN_PROFILE_NORMAL;
|
| 143 |
|
| 144 | case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
|
| 145 | return SSAM_FAN_PROFILE_BETTER_PERFORMANCE;
|
| 146 |
|
| 147 | case PLATFORM_PROFILE_PERFORMANCE:
|
| 148 | return SSAM_FAN_PROFILE_BEST_PERFORMANCE;
|
| 149 |
|
| 150 | default:
|
| 151 |
|
| 152 | WARN(true, "unsupported platform profile");
|
| 153 | return -EOPNOTSUPP;
|
| 154 | }
|
| 155 | }
|
| 156 |
|
| 157 | static int ssam_platform_profile_get(struct device *dev,
|
| 158 | enum platform_profile_option *profile)
|
| 159 | {
|
| 160 | struct ssam_platform_profile_device *tpd;
|
| 161 | enum ssam_tmp_profile tp;
|
| 162 | int status;
|
| 163 |
|
| 164 | tpd = dev_get_drvdata(dev);
|
| 165 |
|
| 166 | status = ssam_tmp_profile_get(tpd->sdev, &tp);
|
| 167 | if (status)
|
| 168 | return status;
|
| 169 |
|
| 170 | status = convert_ssam_tmp_to_profile(tpd->sdev, tp);
|
| 171 | if (status < 0)
|
| 172 | return status;
|
| 173 |
|
| 174 | *profile = status;
|
| 175 | return 0;
|
| 176 | }
|
| 177 |
|
| 178 | static int ssam_platform_profile_set(struct device *dev,
|
| 179 | enum platform_profile_option profile)
|
| 180 | {
|
| 181 | struct ssam_platform_profile_device *tpd;
|
| 182 | int tp;
|
| 183 |
|
| 184 | tpd = dev_get_drvdata(dev);
|
| 185 |
|
| 186 | tp = convert_profile_to_ssam_tmp(tpd->sdev, profile);
|
| 187 | if (tp < 0)
|
| 188 | return tp;
|
| 189 |
|
| 190 | tp = ssam_tmp_profile_set(tpd->sdev, tp);
|
| 191 | if (tp < 0)
|
| 192 | return tp;
|
| 193 |
|
| 194 | if (tpd->has_fan) {
|
| 195 | tp = convert_profile_to_ssam_fan(tpd->sdev, profile);
|
| 196 | if (tp < 0)
|
| 197 | return tp;
|
| 198 | tp = ssam_fan_profile_set(tpd->sdev, tp);
|
| 199 | }
|
| 200 |
|
| 201 | return tp;
|
| 202 | }
|
| 203 |
|
| 204 | static int ssam_platform_profile_probe(void *drvdata, unsigned long *choices)
|
| 205 | {
|
| 206 | set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
|
| 207 | set_bit(PLATFORM_PROFILE_BALANCED, choices);
|
| 208 | set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
|
| 209 | set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
|
| 210 |
|
| 211 | return 0;
|
| 212 | }
|
| 213 |
|
| 214 | static const struct platform_profile_ops ssam_platform_profile_ops = {
|
| 215 | .probe = ssam_platform_profile_probe,
|
| 216 | .profile_get = ssam_platform_profile_get,
|
| 217 | .profile_set = ssam_platform_profile_set,
|
| 218 | };
|
| 219 |
|
| 220 | static int surface_platform_profile_probe(struct ssam_device *sdev)
|
| 221 | {
|
| 222 | struct ssam_platform_profile_device *tpd;
|
| 223 |
|
| 224 | tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
|
| 225 | if (!tpd)
|
| 226 | return -ENOMEM;
|
| 227 |
|
| 228 | tpd->sdev = sdev;
|
| 229 | ssam_device_set_drvdata(sdev, tpd);
|
| 230 |
|
| 231 | tpd->has_fan = device_property_read_bool(&sdev->dev, "has_fan");
|
| 232 |
|
| 233 | tpd->ppdev = devm_platform_profile_register(&sdev->dev, "Surface Platform Profile",
|
| 234 | tpd, &ssam_platform_profile_ops);
|
| 235 |
|
| 236 | return PTR_ERR_OR_ZERO(tpd->ppdev);
|
| 237 | }
|
| 238 |
|
| 239 | static const struct ssam_device_id ssam_platform_profile_match[] = {
|
| 240 | { SSAM_SDEV(TMP, SAM, 0x00, 0x01) },
|
| 241 | { },
|
| 242 | };
|
| 243 | MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
|
| 244 |
|
| 245 | static struct ssam_device_driver surface_platform_profile = {
|
| 246 | .probe = surface_platform_profile_probe,
|
| 247 | .match_table = ssam_platform_profile_match,
|
| 248 | .driver = {
|
| 249 | .name = "surface_platform_profile",
|
| 250 | .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
| 251 | },
|
| 252 | };
|
| 253 | module_ssam_device_driver(surface_platform_profile);
|
| 254 |
|
| 255 | MODULE_AUTHOR("Maximilian Luz <[email protected]>");
|
| 256 | MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
|
| 257 | MODULE_LICENSE("GPL");
|