brintos

brintos / linux-shallow public Read only

0
0
Text · 8.3 KiB · 364199b Raw
375 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * System Control and Management Interface(SCMI) based hwmon sensor driver4 *5 * Copyright (C) 2018-2021 ARM Ltd.6 * Sudeep Holla <sudeep.holla@arm.com>7 */8 9#include <linux/hwmon.h>10#include <linux/module.h>11#include <linux/scmi_protocol.h>12#include <linux/slab.h>13#include <linux/sysfs.h>14#include <linux/thermal.h>15 16static const struct scmi_sensor_proto_ops *sensor_ops;17 18struct scmi_sensors {19	const struct scmi_protocol_handle *ph;20	const struct scmi_sensor_info **info[hwmon_max];21};22 23struct scmi_thermal_sensor {24	const struct scmi_protocol_handle *ph;25	const struct scmi_sensor_info *info;26};27 28static inline u64 __pow10(u8 x)29{30	u64 r = 1;31 32	while (x--)33		r *= 10;34 35	return r;36}37 38static int scmi_hwmon_scale(const struct scmi_sensor_info *sensor, u64 *value)39{40	int scale = sensor->scale;41	u64 f;42 43	switch (sensor->type) {44	case TEMPERATURE_C:45	case VOLTAGE:46	case CURRENT:47		scale += 3;48		break;49	case POWER:50	case ENERGY:51		scale += 6;52		break;53	default:54		break;55	}56 57	if (scale == 0)58		return 0;59 60	if (abs(scale) > 19)61		return -E2BIG;62 63	f = __pow10(abs(scale));64	if (scale > 0)65		*value *= f;66	else67		*value = div64_u64(*value, f);68 69	return 0;70}71 72static int scmi_hwmon_read_scaled_value(const struct scmi_protocol_handle *ph,73					const struct scmi_sensor_info *sensor,74					long *val)75{76	int ret;77	u64 value;78 79	ret = sensor_ops->reading_get(ph, sensor->id, &value);80	if (ret)81		return ret;82 83	ret = scmi_hwmon_scale(sensor, &value);84	if (!ret)85		*val = value;86 87	return ret;88}89 90static int scmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type,91			   u32 attr, int channel, long *val)92{93	const struct scmi_sensor_info *sensor;94	struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);95 96	sensor = *(scmi_sensors->info[type] + channel);97 98	return scmi_hwmon_read_scaled_value(scmi_sensors->ph, sensor, val);99}100 101static int102scmi_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,103		       u32 attr, int channel, const char **str)104{105	const struct scmi_sensor_info *sensor;106	struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev);107 108	sensor = *(scmi_sensors->info[type] + channel);109	*str = sensor->name;110 111	return 0;112}113 114static umode_t115scmi_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type,116		      u32 attr, int channel)117{118	const struct scmi_sensor_info *sensor;119	const struct scmi_sensors *scmi_sensors = drvdata;120 121	sensor = *(scmi_sensors->info[type] + channel);122	if (sensor)123		return 0444;124 125	return 0;126}127 128static const struct hwmon_ops scmi_hwmon_ops = {129	.is_visible = scmi_hwmon_is_visible,130	.read = scmi_hwmon_read,131	.read_string = scmi_hwmon_read_string,132};133 134static struct hwmon_chip_info scmi_chip_info = {135	.ops = &scmi_hwmon_ops,136	.info = NULL,137};138 139static int scmi_hwmon_thermal_get_temp(struct thermal_zone_device *tz,140				       int *temp)141{142	int ret;143	long value;144	struct scmi_thermal_sensor *th_sensor = thermal_zone_device_priv(tz);145 146	ret = scmi_hwmon_read_scaled_value(th_sensor->ph, th_sensor->info,147					   &value);148	if (!ret)149		*temp = value;150 151	return ret;152}153 154static const struct thermal_zone_device_ops scmi_hwmon_thermal_ops = {155	.get_temp = scmi_hwmon_thermal_get_temp,156};157 158static int scmi_hwmon_add_chan_info(struct hwmon_channel_info *scmi_hwmon_chan,159				    struct device *dev, int num,160				    enum hwmon_sensor_types type, u32 config)161{162	int i;163	u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL);164 165	if (!cfg)166		return -ENOMEM;167 168	scmi_hwmon_chan->type = type;169	scmi_hwmon_chan->config = cfg;170	for (i = 0; i < num; i++, cfg++)171		*cfg = config;172 173	return 0;174}175 176static enum hwmon_sensor_types scmi_types[] = {177	[TEMPERATURE_C] = hwmon_temp,178	[VOLTAGE] = hwmon_in,179	[CURRENT] = hwmon_curr,180	[POWER] = hwmon_power,181	[ENERGY] = hwmon_energy,182};183 184static u32 hwmon_attributes[hwmon_max] = {185	[hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL,186	[hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL,187	[hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL,188	[hwmon_power] = HWMON_P_INPUT | HWMON_P_LABEL,189	[hwmon_energy] = HWMON_E_INPUT | HWMON_E_LABEL,190};191 192static int scmi_thermal_sensor_register(struct device *dev,193					const struct scmi_protocol_handle *ph,194					const struct scmi_sensor_info *sensor)195{196	struct scmi_thermal_sensor *th_sensor;197	struct thermal_zone_device *tzd;198 199	th_sensor = devm_kzalloc(dev, sizeof(*th_sensor), GFP_KERNEL);200	if (!th_sensor)201		return -ENOMEM;202 203	th_sensor->ph = ph;204	th_sensor->info = sensor;205 206	/*207	 * Try to register a temperature sensor with the Thermal Framework:208	 * skip sensors not defined as part of any thermal zone (-ENODEV) but209	 * report any other errors related to misconfigured zones/sensors.210	 */211	tzd = devm_thermal_of_zone_register(dev, th_sensor->info->id, th_sensor,212					    &scmi_hwmon_thermal_ops);213	if (IS_ERR(tzd)) {214		devm_kfree(dev, th_sensor);215 216		if (PTR_ERR(tzd) != -ENODEV)217			return PTR_ERR(tzd);218 219		dev_dbg(dev, "Sensor '%s' not attached to any thermal zone.\n",220			sensor->name);221	} else {222		dev_dbg(dev, "Sensor '%s' attached to thermal zone ID:%d\n",223			sensor->name, thermal_zone_device_id(tzd));224	}225 226	return 0;227}228 229static int scmi_hwmon_probe(struct scmi_device *sdev)230{231	int i, idx;232	u16 nr_sensors;233	enum hwmon_sensor_types type;234	struct scmi_sensors *scmi_sensors;235	const struct scmi_sensor_info *sensor;236	int nr_count[hwmon_max] = {0}, nr_types = 0, nr_count_temp = 0;237	const struct hwmon_chip_info *chip_info;238	struct device *hwdev, *dev = &sdev->dev;239	struct hwmon_channel_info *scmi_hwmon_chan;240	const struct hwmon_channel_info **ptr_scmi_ci;241	const struct scmi_handle *handle = sdev->handle;242	struct scmi_protocol_handle *ph;243 244	if (!handle)245		return -ENODEV;246 247	sensor_ops = handle->devm_protocol_get(sdev, SCMI_PROTOCOL_SENSOR, &ph);248	if (IS_ERR(sensor_ops))249		return PTR_ERR(sensor_ops);250 251	nr_sensors = sensor_ops->count_get(ph);252	if (!nr_sensors)253		return -EIO;254 255	scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL);256	if (!scmi_sensors)257		return -ENOMEM;258 259	scmi_sensors->ph = ph;260 261	for (i = 0; i < nr_sensors; i++) {262		sensor = sensor_ops->info_get(ph, i);263		if (!sensor)264			return -EINVAL;265 266		switch (sensor->type) {267		case TEMPERATURE_C:268		case VOLTAGE:269		case CURRENT:270		case POWER:271		case ENERGY:272			type = scmi_types[sensor->type];273			if (!nr_count[type])274				nr_types++;275			nr_count[type]++;276			break;277		}278	}279 280	if (nr_count[hwmon_temp])281		nr_count_temp = nr_count[hwmon_temp];282 283	scmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*scmi_hwmon_chan),284				       GFP_KERNEL);285	if (!scmi_hwmon_chan)286		return -ENOMEM;287 288	ptr_scmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*ptr_scmi_ci),289				   GFP_KERNEL);290	if (!ptr_scmi_ci)291		return -ENOMEM;292 293	scmi_chip_info.info = ptr_scmi_ci;294	chip_info = &scmi_chip_info;295 296	for (type = 0; type < hwmon_max; type++) {297		if (!nr_count[type])298			continue;299 300		scmi_hwmon_add_chan_info(scmi_hwmon_chan, dev, nr_count[type],301					 type, hwmon_attributes[type]);302		*ptr_scmi_ci++ = scmi_hwmon_chan++;303 304		scmi_sensors->info[type] =305			devm_kcalloc(dev, nr_count[type],306				     sizeof(*scmi_sensors->info), GFP_KERNEL);307		if (!scmi_sensors->info[type])308			return -ENOMEM;309	}310 311	for (i = nr_sensors - 1; i >= 0 ; i--) {312		sensor = sensor_ops->info_get(ph, i);313		if (!sensor)314			continue;315 316		switch (sensor->type) {317		case TEMPERATURE_C:318		case VOLTAGE:319		case CURRENT:320		case POWER:321		case ENERGY:322			type = scmi_types[sensor->type];323			idx = --nr_count[type];324			*(scmi_sensors->info[type] + idx) = sensor;325			break;326		}327	}328 329	hwdev = devm_hwmon_device_register_with_info(dev, "scmi_sensors",330						     scmi_sensors, chip_info,331						     NULL);332	if (IS_ERR(hwdev))333		return PTR_ERR(hwdev);334 335	for (i = 0; i < nr_count_temp; i++) {336		int ret;337 338		sensor = *(scmi_sensors->info[hwmon_temp] + i);339		if (!sensor)340			continue;341 342		/*343		 * Warn on any misconfiguration related to thermal zones but344		 * bail out of probing only on memory errors.345		 */346		ret = scmi_thermal_sensor_register(dev, ph, sensor);347		if (ret) {348			if (ret == -ENOMEM)349				return ret;350			dev_warn(dev,351				 "Thermal zone misconfigured for %s. err=%d\n",352				 sensor->name, ret);353		}354	}355 356	return 0;357}358 359static const struct scmi_device_id scmi_id_table[] = {360	{ SCMI_PROTOCOL_SENSOR, "hwmon" },361	{ },362};363MODULE_DEVICE_TABLE(scmi, scmi_id_table);364 365static struct scmi_driver scmi_hwmon_drv = {366	.name		= "scmi-hwmon",367	.probe		= scmi_hwmon_probe,368	.id_table	= scmi_id_table,369};370module_scmi_driver(scmi_hwmon_drv);371 372MODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>");373MODULE_DESCRIPTION("ARM SCMI HWMON interface driver");374MODULE_LICENSE("GPL v2");375