1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * power_supply_hwmon.c - power supply hwmon support.
4 */
5
6 #include <linux/err.h>
7 #include <linux/hwmon.h>
8 #include <linux/power_supply.h>
9 #include <linux/slab.h>
10
11 struct power_supply_hwmon {
12 struct power_supply *psy;
13 unsigned long *props;
14 };
15
16 static const char *const ps_temp_label[] = {
17 "temp",
18 "ambient temp",
19 };
20
power_supply_hwmon_in_to_property(u32 attr)21 static int power_supply_hwmon_in_to_property(u32 attr)
22 {
23 switch (attr) {
24 case hwmon_in_average:
25 return POWER_SUPPLY_PROP_VOLTAGE_AVG;
26 case hwmon_in_min:
27 return POWER_SUPPLY_PROP_VOLTAGE_MIN;
28 case hwmon_in_max:
29 return POWER_SUPPLY_PROP_VOLTAGE_MAX;
30 case hwmon_in_input:
31 return POWER_SUPPLY_PROP_VOLTAGE_NOW;
32 default:
33 return -EINVAL;
34 }
35 }
36
power_supply_hwmon_curr_to_property(u32 attr)37 static int power_supply_hwmon_curr_to_property(u32 attr)
38 {
39 switch (attr) {
40 case hwmon_curr_average:
41 return POWER_SUPPLY_PROP_CURRENT_AVG;
42 case hwmon_curr_max:
43 return POWER_SUPPLY_PROP_CURRENT_MAX;
44 case hwmon_curr_input:
45 return POWER_SUPPLY_PROP_CURRENT_NOW;
46 default:
47 return -EINVAL;
48 }
49 }
50
power_supply_hwmon_power_to_property(u32 attr)51 static int power_supply_hwmon_power_to_property(u32 attr)
52 {
53 switch (attr) {
54 case hwmon_power_input:
55 return POWER_SUPPLY_PROP_POWER_NOW;
56 case hwmon_power_average:
57 return POWER_SUPPLY_PROP_POWER_AVG;
58 default:
59 return -EINVAL;
60 }
61 }
62
power_supply_hwmon_temp_to_property(u32 attr,int channel)63 static int power_supply_hwmon_temp_to_property(u32 attr, int channel)
64 {
65 if (channel) {
66 switch (attr) {
67 case hwmon_temp_input:
68 return POWER_SUPPLY_PROP_TEMP_AMBIENT;
69 case hwmon_temp_min_alarm:
70 return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN;
71 case hwmon_temp_max_alarm:
72 return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX;
73 default:
74 break;
75 }
76 } else {
77 switch (attr) {
78 case hwmon_temp_input:
79 return POWER_SUPPLY_PROP_TEMP;
80 case hwmon_temp_max:
81 return POWER_SUPPLY_PROP_TEMP_MAX;
82 case hwmon_temp_min:
83 return POWER_SUPPLY_PROP_TEMP_MIN;
84 case hwmon_temp_min_alarm:
85 return POWER_SUPPLY_PROP_TEMP_ALERT_MIN;
86 case hwmon_temp_max_alarm:
87 return POWER_SUPPLY_PROP_TEMP_ALERT_MAX;
88 default:
89 break;
90 }
91 }
92
93 return -EINVAL;
94 }
95
96 static int
power_supply_hwmon_to_property(enum hwmon_sensor_types type,u32 attr,int channel)97 power_supply_hwmon_to_property(enum hwmon_sensor_types type,
98 u32 attr, int channel)
99 {
100 switch (type) {
101 case hwmon_in:
102 return power_supply_hwmon_in_to_property(attr);
103 case hwmon_curr:
104 return power_supply_hwmon_curr_to_property(attr);
105 case hwmon_power:
106 return power_supply_hwmon_power_to_property(attr);
107 case hwmon_temp:
108 return power_supply_hwmon_temp_to_property(attr, channel);
109 default:
110 return -EINVAL;
111 }
112 }
113
power_supply_hwmon_is_a_label(enum hwmon_sensor_types type,u32 attr)114 static bool power_supply_hwmon_is_a_label(enum hwmon_sensor_types type,
115 u32 attr)
116 {
117 return type == hwmon_temp && attr == hwmon_temp_label;
118 }
119
120 struct hwmon_type_attr_list {
121 const u32 *attrs;
122 size_t n_attrs;
123 };
124
125 static const u32 ps_temp_attrs[] = {
126 hwmon_temp_input,
127 hwmon_temp_min, hwmon_temp_max,
128 hwmon_temp_min_alarm, hwmon_temp_max_alarm,
129 };
130
131 static const struct hwmon_type_attr_list ps_type_attrs[hwmon_max] = {
132 [hwmon_temp] = { ps_temp_attrs, ARRAY_SIZE(ps_temp_attrs) },
133 };
134
power_supply_hwmon_has_input(const struct power_supply_hwmon * psyhw,enum hwmon_sensor_types type,int channel)135 static bool power_supply_hwmon_has_input(
136 const struct power_supply_hwmon *psyhw,
137 enum hwmon_sensor_types type, int channel)
138 {
139 const struct hwmon_type_attr_list *attr_list = &ps_type_attrs[type];
140 size_t i;
141
142 for (i = 0; i < attr_list->n_attrs; ++i) {
143 int prop = power_supply_hwmon_to_property(type,
144 attr_list->attrs[i], channel);
145
146 if (prop >= 0 && test_bit(prop, psyhw->props))
147 return true;
148 }
149
150 return false;
151 }
152
power_supply_hwmon_is_writable(enum hwmon_sensor_types type,u32 attr)153 static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type,
154 u32 attr)
155 {
156 switch (type) {
157 case hwmon_in:
158 return attr == hwmon_in_min ||
159 attr == hwmon_in_max;
160 case hwmon_curr:
161 return attr == hwmon_curr_max;
162 case hwmon_temp:
163 return attr == hwmon_temp_max ||
164 attr == hwmon_temp_min ||
165 attr == hwmon_temp_min_alarm ||
166 attr == hwmon_temp_max_alarm;
167 default:
168 return false;
169 }
170 }
171
power_supply_hwmon_is_visible(const void * data,enum hwmon_sensor_types type,u32 attr,int channel)172 static umode_t power_supply_hwmon_is_visible(const void *data,
173 enum hwmon_sensor_types type,
174 u32 attr, int channel)
175 {
176 const struct power_supply_hwmon *psyhw = data;
177 int prop;
178
179 if (power_supply_hwmon_is_a_label(type, attr)) {
180 if (power_supply_hwmon_has_input(psyhw, type, channel))
181 return 0444;
182 else
183 return 0;
184 }
185
186 prop = power_supply_hwmon_to_property(type, attr, channel);
187 if (prop < 0 || !test_bit(prop, psyhw->props))
188 return 0;
189
190 if (power_supply_property_is_writeable(psyhw->psy, prop) > 0 &&
191 power_supply_hwmon_is_writable(type, attr))
192 return 0644;
193
194 return 0444;
195 }
196
power_supply_hwmon_read_string(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,const char ** str)197 static int power_supply_hwmon_read_string(struct device *dev,
198 enum hwmon_sensor_types type,
199 u32 attr, int channel,
200 const char **str)
201 {
202 switch (type) {
203 case hwmon_temp:
204 *str = ps_temp_label[channel];
205 break;
206 default:
207 /* unreachable, but see:
208 * gcc bug #51513 [1] and clang bug #978 [2]
209 *
210 * [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51513
211 * [2] https://github.com/ClangBuiltLinux/linux/issues/978
212 */
213 break;
214 }
215
216 return 0;
217 }
218
219 static int
power_supply_hwmon_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)220 power_supply_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
221 u32 attr, int channel, long *val)
222 {
223 struct power_supply_hwmon *psyhw = dev_get_drvdata(dev);
224 struct power_supply *psy = psyhw->psy;
225 union power_supply_propval pspval;
226 int ret, prop;
227
228 prop = power_supply_hwmon_to_property(type, attr, channel);
229 if (prop < 0)
230 return prop;
231
232 ret = power_supply_get_property(psy, prop, &pspval);
233 if (ret)
234 return ret;
235
236 switch (type) {
237 /*
238 * Both voltage and current is reported in units of
239 * microvolts/microamps, so we need to adjust it to
240 * milliamps(volts)
241 */
242 case hwmon_curr:
243 case hwmon_in:
244 pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 1000);
245 break;
246 case hwmon_power:
247 /*
248 * Power properties are already in microwatts.
249 */
250 break;
251 /*
252 * Temp needs to be converted from 1/10 C to milli-C
253 */
254 case hwmon_temp:
255 if (check_mul_overflow(pspval.intval, 100,
256 &pspval.intval))
257 return -EOVERFLOW;
258 break;
259 default:
260 return -EINVAL;
261 }
262
263 *val = pspval.intval;
264
265 return 0;
266 }
267
268 static int
power_supply_hwmon_write(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long val)269 power_supply_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
270 u32 attr, int channel, long val)
271 {
272 struct power_supply_hwmon *psyhw = dev_get_drvdata(dev);
273 struct power_supply *psy = psyhw->psy;
274 union power_supply_propval pspval;
275 int prop;
276
277 prop = power_supply_hwmon_to_property(type, attr, channel);
278 if (prop < 0)
279 return prop;
280
281 pspval.intval = val;
282
283 switch (type) {
284 /*
285 * Both voltage and current is reported in units of
286 * microvolts/microamps, so we need to adjust it to
287 * milliamps(volts)
288 */
289 case hwmon_curr:
290 case hwmon_in:
291 if (check_mul_overflow(pspval.intval, 1000,
292 &pspval.intval))
293 return -EOVERFLOW;
294 break;
295 /*
296 * Temp needs to be converted from 1/10 C to milli-C
297 */
298 case hwmon_temp:
299 pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 100);
300 break;
301 default:
302 return -EINVAL;
303 }
304
305 return power_supply_set_property(psy, prop, &pspval);
306 }
307
308 static const struct hwmon_ops power_supply_hwmon_ops = {
309 .is_visible = power_supply_hwmon_is_visible,
310 .read = power_supply_hwmon_read,
311 .write = power_supply_hwmon_write,
312 .read_string = power_supply_hwmon_read_string,
313 };
314
315 static const struct hwmon_channel_info * const power_supply_hwmon_info[] = {
316 HWMON_CHANNEL_INFO(temp,
317 HWMON_T_LABEL |
318 HWMON_T_INPUT |
319 HWMON_T_MAX |
320 HWMON_T_MIN |
321 HWMON_T_MIN_ALARM |
322 HWMON_T_MAX_ALARM,
323
324 HWMON_T_LABEL |
325 HWMON_T_INPUT |
326 HWMON_T_MIN_ALARM |
327 HWMON_T_MAX_ALARM),
328
329 HWMON_CHANNEL_INFO(curr,
330 HWMON_C_AVERAGE |
331 HWMON_C_MAX |
332 HWMON_C_INPUT),
333
334 HWMON_CHANNEL_INFO(power,
335 HWMON_P_INPUT |
336 HWMON_P_AVERAGE),
337
338 HWMON_CHANNEL_INFO(in,
339 HWMON_I_AVERAGE |
340 HWMON_I_MIN |
341 HWMON_I_MAX |
342 HWMON_I_INPUT),
343 NULL
344 };
345
346 static const struct hwmon_chip_info power_supply_hwmon_chip_info = {
347 .ops = &power_supply_hwmon_ops,
348 .info = power_supply_hwmon_info,
349 };
350
power_supply_add_hwmon_sysfs(struct power_supply * psy)351 int power_supply_add_hwmon_sysfs(struct power_supply *psy)
352 {
353 const struct power_supply_desc *desc = psy->desc;
354 struct power_supply_hwmon *psyhw;
355 struct device *dev = &psy->dev;
356 struct device *hwmon;
357 int ret, i;
358 const char *name;
359
360 if (!devres_open_group(dev, power_supply_add_hwmon_sysfs,
361 GFP_KERNEL))
362 return -ENOMEM;
363
364 psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL);
365 if (!psyhw) {
366 ret = -ENOMEM;
367 goto error;
368 }
369
370 psyhw->psy = psy;
371 psyhw->props = devm_bitmap_zalloc(dev,
372 POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1,
373 GFP_KERNEL);
374 if (!psyhw->props) {
375 ret = -ENOMEM;
376 goto error;
377 }
378
379 for (i = 0; i < desc->num_properties; i++) {
380 const enum power_supply_property prop = desc->properties[i];
381
382 switch (prop) {
383 case POWER_SUPPLY_PROP_CURRENT_AVG:
384 case POWER_SUPPLY_PROP_CURRENT_MAX:
385 case POWER_SUPPLY_PROP_CURRENT_NOW:
386 case POWER_SUPPLY_PROP_POWER_AVG:
387 case POWER_SUPPLY_PROP_POWER_NOW:
388 case POWER_SUPPLY_PROP_TEMP:
389 case POWER_SUPPLY_PROP_TEMP_MAX:
390 case POWER_SUPPLY_PROP_TEMP_MIN:
391 case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
392 case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
393 case POWER_SUPPLY_PROP_TEMP_AMBIENT:
394 case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN:
395 case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX:
396 case POWER_SUPPLY_PROP_VOLTAGE_AVG:
397 case POWER_SUPPLY_PROP_VOLTAGE_MIN:
398 case POWER_SUPPLY_PROP_VOLTAGE_MAX:
399 case POWER_SUPPLY_PROP_VOLTAGE_NOW:
400 set_bit(prop, psyhw->props);
401 break;
402 default:
403 break;
404 }
405 }
406
407 name = psy->desc->name;
408 if (strchr(name, '-')) {
409 char *new_name;
410
411 new_name = devm_kstrdup(dev, name, GFP_KERNEL);
412 if (!new_name) {
413 ret = -ENOMEM;
414 goto error;
415 }
416 strreplace(new_name, '-', '_');
417 name = new_name;
418 }
419 hwmon = devm_hwmon_device_register_with_info(dev, name,
420 psyhw,
421 &power_supply_hwmon_chip_info,
422 NULL);
423 ret = PTR_ERR_OR_ZERO(hwmon);
424 if (ret)
425 goto error;
426
427 devres_close_group(dev, power_supply_add_hwmon_sysfs);
428 return 0;
429 error:
430 devres_release_group(dev, NULL);
431 return ret;
432 }
433
power_supply_remove_hwmon_sysfs(struct power_supply * psy)434 void power_supply_remove_hwmon_sysfs(struct power_supply *psy)
435 {
436 devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs);
437 }
438