0001
0002
0003
0004
0005
0006
0007
0008
0009 #include <asm/unaligned.h>
0010 #include <linux/kernel.h>
0011 #include <linux/module.h>
0012 #include <linux/platform_profile.h>
0013 #include <linux/types.h>
0014
0015 #include <linux/surface_aggregator/device.h>
0016
0017 enum ssam_tmp_profile {
0018 SSAM_TMP_PROFILE_NORMAL = 1,
0019 SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
0020 SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
0021 SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
0022 };
0023
0024 struct ssam_tmp_profile_info {
0025 __le32 profile;
0026 __le16 unknown1;
0027 __le16 unknown2;
0028 } __packed;
0029
0030 struct ssam_tmp_profile_device {
0031 struct ssam_device *sdev;
0032 struct platform_profile_handler handler;
0033 };
0034
0035 SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
0036 .target_category = SSAM_SSH_TC_TMP,
0037 .command_id = 0x02,
0038 });
0039
0040 SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
0041 .target_category = SSAM_SSH_TC_TMP,
0042 .command_id = 0x03,
0043 });
0044
0045 static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
0046 {
0047 struct ssam_tmp_profile_info info;
0048 int status;
0049
0050 status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
0051 if (status < 0)
0052 return status;
0053
0054 *p = le32_to_cpu(info.profile);
0055 return 0;
0056 }
0057
0058 static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
0059 {
0060 __le32 profile_le = cpu_to_le32(p);
0061
0062 return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
0063 }
0064
0065 static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
0066 {
0067 switch (p) {
0068 case SSAM_TMP_PROFILE_NORMAL:
0069 return PLATFORM_PROFILE_BALANCED;
0070
0071 case SSAM_TMP_PROFILE_BATTERY_SAVER:
0072 return PLATFORM_PROFILE_LOW_POWER;
0073
0074 case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
0075 return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
0076
0077 case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
0078 return PLATFORM_PROFILE_PERFORMANCE;
0079
0080 default:
0081 dev_err(&sdev->dev, "invalid performance profile: %d", p);
0082 return -EINVAL;
0083 }
0084 }
0085
0086 static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
0087 {
0088 switch (p) {
0089 case PLATFORM_PROFILE_LOW_POWER:
0090 return SSAM_TMP_PROFILE_BATTERY_SAVER;
0091
0092 case PLATFORM_PROFILE_BALANCED:
0093 return SSAM_TMP_PROFILE_NORMAL;
0094
0095 case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
0096 return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
0097
0098 case PLATFORM_PROFILE_PERFORMANCE:
0099 return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
0100
0101 default:
0102
0103 WARN(true, "unsupported platform profile");
0104 return -EOPNOTSUPP;
0105 }
0106 }
0107
0108 static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
0109 enum platform_profile_option *profile)
0110 {
0111 struct ssam_tmp_profile_device *tpd;
0112 enum ssam_tmp_profile tp;
0113 int status;
0114
0115 tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
0116
0117 status = ssam_tmp_profile_get(tpd->sdev, &tp);
0118 if (status)
0119 return status;
0120
0121 status = convert_ssam_to_profile(tpd->sdev, tp);
0122 if (status < 0)
0123 return status;
0124
0125 *profile = status;
0126 return 0;
0127 }
0128
0129 static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
0130 enum platform_profile_option profile)
0131 {
0132 struct ssam_tmp_profile_device *tpd;
0133 int tp;
0134
0135 tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
0136
0137 tp = convert_profile_to_ssam(tpd->sdev, profile);
0138 if (tp < 0)
0139 return tp;
0140
0141 return ssam_tmp_profile_set(tpd->sdev, tp);
0142 }
0143
0144 static int surface_platform_profile_probe(struct ssam_device *sdev)
0145 {
0146 struct ssam_tmp_profile_device *tpd;
0147
0148 tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
0149 if (!tpd)
0150 return -ENOMEM;
0151
0152 tpd->sdev = sdev;
0153
0154 tpd->handler.profile_get = ssam_platform_profile_get;
0155 tpd->handler.profile_set = ssam_platform_profile_set;
0156
0157 set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
0158 set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
0159 set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
0160 set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices);
0161
0162 platform_profile_register(&tpd->handler);
0163 return 0;
0164 }
0165
0166 static void surface_platform_profile_remove(struct ssam_device *sdev)
0167 {
0168 platform_profile_remove();
0169 }
0170
0171 static const struct ssam_device_id ssam_platform_profile_match[] = {
0172 { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) },
0173 { },
0174 };
0175 MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
0176
0177 static struct ssam_device_driver surface_platform_profile = {
0178 .probe = surface_platform_profile_probe,
0179 .remove = surface_platform_profile_remove,
0180 .match_table = ssam_platform_profile_match,
0181 .driver = {
0182 .name = "surface_platform_profile",
0183 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
0184 },
0185 };
0186 module_ssam_device_driver(surface_platform_profile);
0187
0188 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
0189 MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
0190 MODULE_LICENSE("GPL");