brintos

brintos / linux-shallow public Read only

0
0
Text · 9.8 KiB · 6fbbfb1 Raw
438 lines · c
1// SPDX-License-Identifier: GPL-2.02/*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 11struct power_supply_hwmon {12	struct power_supply *psy;13	unsigned long *props;14};15 16static const char *const ps_temp_label[] = {17	"temp",18	"ambient temp",19};20 21static 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 37static 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 51static 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 63static 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 96static int97power_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 114static 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 120struct hwmon_type_attr_list {121	const u32 *attrs;122	size_t n_attrs;123};124 125static 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 131static const struct hwmon_type_attr_list ps_type_attrs[hwmon_max] = {132	[hwmon_temp] = { ps_temp_attrs, ARRAY_SIZE(ps_temp_attrs) },133};134 135static 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 153static 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 172static 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		else183			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 197static 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=51513211		 * [2] https://github.com/ClangBuiltLinux/linux/issues/978212		 */213		break;214	}215 216	return 0;217}218 219static int220power_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 of239	 * microvolts/microamps, so we need to adjust it to240	 * 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-C253	 */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 268static int269power_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 of286	 * microvolts/microamps, so we need to adjust it to287	 * 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-C297	 */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 308static 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 315static 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	NULL344};345 346static const struct hwmon_chip_info power_supply_hwmon_chip_info = {347	.ops = &power_supply_hwmon_ops,348	.info = power_supply_hwmon_info,349};350 351int 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;429error:430	devres_release_group(dev, NULL);431	return ret;432}433 434void power_supply_remove_hwmon_sysfs(struct power_supply *psy)435{436	devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs);437}438