1 // SPDX-License-Identifier: GPL-2.0-or-later
2 
3 /* Platform profile sysfs interface */
4 
5 #include <linux/acpi.h>
6 #include <linux/bits.h>
7 #include <linux/init.h>
8 #include <linux/mutex.h>
9 #include <linux/platform_profile.h>
10 #include <linux/sysfs.h>
11 
12 static struct platform_profile_handler *cur_profile;
13 static DEFINE_MUTEX(profile_lock);
14 
15 static const char * const profile_names[] = {
16 	[PLATFORM_PROFILE_LOW_POWER] = "low-power",
17 	[PLATFORM_PROFILE_COOL] = "cool",
18 	[PLATFORM_PROFILE_QUIET] = "quiet",
19 	[PLATFORM_PROFILE_BALANCED] = "balanced",
20 	[PLATFORM_PROFILE_PERFORMANCE] = "performance",
21 };
22 static_assert(ARRAY_SIZE(profile_names) == PLATFORM_PROFILE_LAST);
23 
24 static ssize_t platform_profile_choices_show(struct device *dev,
25 					struct device_attribute *attr,
26 					char *buf)
27 {
28 	int len = 0;
29 	int err, i;
30 
31 	err = mutex_lock_interruptible(&profile_lock);
32 	if (err)
33 		return err;
34 
35 	if (!cur_profile) {
36 		mutex_unlock(&profile_lock);
37 		return -ENODEV;
38 	}
39 
40 	for_each_set_bit(i, cur_profile->choices, PLATFORM_PROFILE_LAST) {
41 		if (len == 0)
42 			len += sysfs_emit_at(buf, len, "%s", profile_names[i]);
43 		else
44 			len += sysfs_emit_at(buf, len, " %s", profile_names[i]);
45 	}
46 	len += sysfs_emit_at(buf, len, "\n");
47 	mutex_unlock(&profile_lock);
48 	return len;
49 }
50 
51 static ssize_t platform_profile_show(struct device *dev,
52 					struct device_attribute *attr,
53 					char *buf)
54 {
55 	enum platform_profile_option profile = PLATFORM_PROFILE_BALANCED;
56 	int err;
57 
58 	err = mutex_lock_interruptible(&profile_lock);
59 	if (err)
60 		return err;
61 
62 	if (!cur_profile) {
63 		mutex_unlock(&profile_lock);
64 		return -ENODEV;
65 	}
66 
67 	err = cur_profile->profile_get(cur_profile, &profile);
68 	mutex_unlock(&profile_lock);
69 	if (err)
70 		return err;
71 
72 	/* Check that profile is valid index */
73 	if (WARN_ON((profile < 0) || (profile >= ARRAY_SIZE(profile_names))))
74 		return -EIO;
75 
76 	return sysfs_emit(buf, "%s\n", profile_names[profile]);
77 }
78 
79 static ssize_t platform_profile_store(struct device *dev,
80 			    struct device_attribute *attr,
81 			    const char *buf, size_t count)
82 {
83 	int err, i;
84 
85 	err = mutex_lock_interruptible(&profile_lock);
86 	if (err)
87 		return err;
88 
89 	if (!cur_profile) {
90 		mutex_unlock(&profile_lock);
91 		return -ENODEV;
92 	}
93 
94 	/* Scan for a matching profile */
95 	i = sysfs_match_string(profile_names, buf);
96 	if (i < 0) {
97 		mutex_unlock(&profile_lock);
98 		return -EINVAL;
99 	}
100 
101 	/* Check that platform supports this profile choice */
102 	if (!test_bit(i, cur_profile->choices)) {
103 		mutex_unlock(&profile_lock);
104 		return -EOPNOTSUPP;
105 	}
106 
107 	err = cur_profile->profile_set(cur_profile, i);
108 	mutex_unlock(&profile_lock);
109 	if (err)
110 		return err;
111 	return count;
112 }
113 
114 static DEVICE_ATTR_RO(platform_profile_choices);
115 static DEVICE_ATTR_RW(platform_profile);
116 
117 static struct attribute *platform_profile_attrs[] = {
118 	&dev_attr_platform_profile_choices.attr,
119 	&dev_attr_platform_profile.attr,
120 	NULL
121 };
122 
123 static const struct attribute_group platform_profile_group = {
124 	.attrs = platform_profile_attrs
125 };
126 
127 void platform_profile_notify(void)
128 {
129 	if (!cur_profile)
130 		return;
131 	sysfs_notify(acpi_kobj, NULL, "platform_profile");
132 }
133 EXPORT_SYMBOL_GPL(platform_profile_notify);
134 
135 int platform_profile_register(struct platform_profile_handler *pprof)
136 {
137 	int err;
138 
139 	mutex_lock(&profile_lock);
140 	/* We can only have one active profile */
141 	if (cur_profile) {
142 		mutex_unlock(&profile_lock);
143 		return -EEXIST;
144 	}
145 
146 	/* Sanity check the profile handler field are set */
147 	if (!pprof || bitmap_empty(pprof->choices, PLATFORM_PROFILE_LAST) ||
148 		!pprof->profile_set || !pprof->profile_get) {
149 		mutex_unlock(&profile_lock);
150 		return -EINVAL;
151 	}
152 
153 	err = sysfs_create_group(acpi_kobj, &platform_profile_group);
154 	if (err) {
155 		mutex_unlock(&profile_lock);
156 		return err;
157 	}
158 
159 	cur_profile = pprof;
160 	mutex_unlock(&profile_lock);
161 	return 0;
162 }
163 EXPORT_SYMBOL_GPL(platform_profile_register);
164 
165 int platform_profile_remove(void)
166 {
167 	sysfs_remove_group(acpi_kobj, &platform_profile_group);
168 
169 	mutex_lock(&profile_lock);
170 	cur_profile = NULL;
171 	mutex_unlock(&profile_lock);
172 	return 0;
173 }
174 EXPORT_SYMBOL_GPL(platform_profile_remove);
175 
176 MODULE_AUTHOR("Mark Pearson <markpearson@lenovo.com>");
177 MODULE_LICENSE("GPL");
178