xref: /linux/drivers/platform/surface/surface_platform_profile.c (revision 954ea91fb68b771dba6d87cfa61b68e09cc2497f)
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, SAM, 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