18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * System Control and Management Interface(SCMI) based hwmon sensor driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2018 ARM Ltd. 68c2ecf20Sopenharmony_ci * Sudeep Holla <sudeep.holla@arm.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/scmi_protocol.h> 128c2ecf20Sopenharmony_ci#include <linux/slab.h> 138c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 148c2ecf20Sopenharmony_ci#include <linux/thermal.h> 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_cistruct scmi_sensors { 178c2ecf20Sopenharmony_ci const struct scmi_handle *handle; 188c2ecf20Sopenharmony_ci const struct scmi_sensor_info **info[hwmon_max]; 198c2ecf20Sopenharmony_ci}; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic inline u64 __pow10(u8 x) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci u64 r = 1; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci while (x--) 268c2ecf20Sopenharmony_ci r *= 10; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci return r; 298c2ecf20Sopenharmony_ci} 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic int scmi_hwmon_scale(const struct scmi_sensor_info *sensor, u64 *value) 328c2ecf20Sopenharmony_ci{ 338c2ecf20Sopenharmony_ci s8 scale = sensor->scale; 348c2ecf20Sopenharmony_ci u64 f; 358c2ecf20Sopenharmony_ci 368c2ecf20Sopenharmony_ci switch (sensor->type) { 378c2ecf20Sopenharmony_ci case TEMPERATURE_C: 388c2ecf20Sopenharmony_ci case VOLTAGE: 398c2ecf20Sopenharmony_ci case CURRENT: 408c2ecf20Sopenharmony_ci scale += 3; 418c2ecf20Sopenharmony_ci break; 428c2ecf20Sopenharmony_ci case POWER: 438c2ecf20Sopenharmony_ci case ENERGY: 448c2ecf20Sopenharmony_ci scale += 6; 458c2ecf20Sopenharmony_ci break; 468c2ecf20Sopenharmony_ci default: 478c2ecf20Sopenharmony_ci break; 488c2ecf20Sopenharmony_ci } 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci if (scale == 0) 518c2ecf20Sopenharmony_ci return 0; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci if (abs(scale) > 19) 548c2ecf20Sopenharmony_ci return -E2BIG; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci f = __pow10(abs(scale)); 578c2ecf20Sopenharmony_ci if (scale > 0) 588c2ecf20Sopenharmony_ci *value *= f; 598c2ecf20Sopenharmony_ci else 608c2ecf20Sopenharmony_ci *value = div64_u64(*value, f); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci return 0; 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int scmi_hwmon_read(struct device *dev, enum hwmon_sensor_types type, 668c2ecf20Sopenharmony_ci u32 attr, int channel, long *val) 678c2ecf20Sopenharmony_ci{ 688c2ecf20Sopenharmony_ci int ret; 698c2ecf20Sopenharmony_ci u64 value; 708c2ecf20Sopenharmony_ci const struct scmi_sensor_info *sensor; 718c2ecf20Sopenharmony_ci struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev); 728c2ecf20Sopenharmony_ci const struct scmi_handle *h = scmi_sensors->handle; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci sensor = *(scmi_sensors->info[type] + channel); 758c2ecf20Sopenharmony_ci ret = h->sensor_ops->reading_get(h, sensor->id, &value); 768c2ecf20Sopenharmony_ci if (ret) 778c2ecf20Sopenharmony_ci return ret; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci ret = scmi_hwmon_scale(sensor, &value); 808c2ecf20Sopenharmony_ci if (!ret) 818c2ecf20Sopenharmony_ci *val = value; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci return ret; 848c2ecf20Sopenharmony_ci} 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int 878c2ecf20Sopenharmony_ciscmi_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, 888c2ecf20Sopenharmony_ci u32 attr, int channel, const char **str) 898c2ecf20Sopenharmony_ci{ 908c2ecf20Sopenharmony_ci const struct scmi_sensor_info *sensor; 918c2ecf20Sopenharmony_ci struct scmi_sensors *scmi_sensors = dev_get_drvdata(dev); 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci sensor = *(scmi_sensors->info[type] + channel); 948c2ecf20Sopenharmony_ci *str = sensor->name; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci return 0; 978c2ecf20Sopenharmony_ci} 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_cistatic umode_t 1008c2ecf20Sopenharmony_ciscmi_hwmon_is_visible(const void *drvdata, enum hwmon_sensor_types type, 1018c2ecf20Sopenharmony_ci u32 attr, int channel) 1028c2ecf20Sopenharmony_ci{ 1038c2ecf20Sopenharmony_ci const struct scmi_sensor_info *sensor; 1048c2ecf20Sopenharmony_ci const struct scmi_sensors *scmi_sensors = drvdata; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci sensor = *(scmi_sensors->info[type] + channel); 1078c2ecf20Sopenharmony_ci if (sensor) 1088c2ecf20Sopenharmony_ci return 0444; 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_ci return 0; 1118c2ecf20Sopenharmony_ci} 1128c2ecf20Sopenharmony_ci 1138c2ecf20Sopenharmony_cistatic const struct hwmon_ops scmi_hwmon_ops = { 1148c2ecf20Sopenharmony_ci .is_visible = scmi_hwmon_is_visible, 1158c2ecf20Sopenharmony_ci .read = scmi_hwmon_read, 1168c2ecf20Sopenharmony_ci .read_string = scmi_hwmon_read_string, 1178c2ecf20Sopenharmony_ci}; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_cistatic struct hwmon_chip_info scmi_chip_info = { 1208c2ecf20Sopenharmony_ci .ops = &scmi_hwmon_ops, 1218c2ecf20Sopenharmony_ci .info = NULL, 1228c2ecf20Sopenharmony_ci}; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic int scmi_hwmon_add_chan_info(struct hwmon_channel_info *scmi_hwmon_chan, 1258c2ecf20Sopenharmony_ci struct device *dev, int num, 1268c2ecf20Sopenharmony_ci enum hwmon_sensor_types type, u32 config) 1278c2ecf20Sopenharmony_ci{ 1288c2ecf20Sopenharmony_ci int i; 1298c2ecf20Sopenharmony_ci u32 *cfg = devm_kcalloc(dev, num + 1, sizeof(*cfg), GFP_KERNEL); 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci if (!cfg) 1328c2ecf20Sopenharmony_ci return -ENOMEM; 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_ci scmi_hwmon_chan->type = type; 1358c2ecf20Sopenharmony_ci scmi_hwmon_chan->config = cfg; 1368c2ecf20Sopenharmony_ci for (i = 0; i < num; i++, cfg++) 1378c2ecf20Sopenharmony_ci *cfg = config; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci return 0; 1408c2ecf20Sopenharmony_ci} 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_cistatic enum hwmon_sensor_types scmi_types[] = { 1438c2ecf20Sopenharmony_ci [TEMPERATURE_C] = hwmon_temp, 1448c2ecf20Sopenharmony_ci [VOLTAGE] = hwmon_in, 1458c2ecf20Sopenharmony_ci [CURRENT] = hwmon_curr, 1468c2ecf20Sopenharmony_ci [POWER] = hwmon_power, 1478c2ecf20Sopenharmony_ci [ENERGY] = hwmon_energy, 1488c2ecf20Sopenharmony_ci}; 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_cistatic u32 hwmon_attributes[hwmon_max] = { 1518c2ecf20Sopenharmony_ci [hwmon_chip] = HWMON_C_REGISTER_TZ, 1528c2ecf20Sopenharmony_ci [hwmon_temp] = HWMON_T_INPUT | HWMON_T_LABEL, 1538c2ecf20Sopenharmony_ci [hwmon_in] = HWMON_I_INPUT | HWMON_I_LABEL, 1548c2ecf20Sopenharmony_ci [hwmon_curr] = HWMON_C_INPUT | HWMON_C_LABEL, 1558c2ecf20Sopenharmony_ci [hwmon_power] = HWMON_P_INPUT | HWMON_P_LABEL, 1568c2ecf20Sopenharmony_ci [hwmon_energy] = HWMON_E_INPUT | HWMON_E_LABEL, 1578c2ecf20Sopenharmony_ci}; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int scmi_hwmon_probe(struct scmi_device *sdev) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci int i, idx; 1628c2ecf20Sopenharmony_ci u16 nr_sensors; 1638c2ecf20Sopenharmony_ci enum hwmon_sensor_types type; 1648c2ecf20Sopenharmony_ci struct scmi_sensors *scmi_sensors; 1658c2ecf20Sopenharmony_ci const struct scmi_sensor_info *sensor; 1668c2ecf20Sopenharmony_ci int nr_count[hwmon_max] = {0}, nr_types = 0; 1678c2ecf20Sopenharmony_ci const struct hwmon_chip_info *chip_info; 1688c2ecf20Sopenharmony_ci struct device *hwdev, *dev = &sdev->dev; 1698c2ecf20Sopenharmony_ci struct hwmon_channel_info *scmi_hwmon_chan; 1708c2ecf20Sopenharmony_ci const struct hwmon_channel_info **ptr_scmi_ci; 1718c2ecf20Sopenharmony_ci const struct scmi_handle *handle = sdev->handle; 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci if (!handle || !handle->sensor_ops) 1748c2ecf20Sopenharmony_ci return -ENODEV; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci nr_sensors = handle->sensor_ops->count_get(handle); 1778c2ecf20Sopenharmony_ci if (!nr_sensors) 1788c2ecf20Sopenharmony_ci return -EIO; 1798c2ecf20Sopenharmony_ci 1808c2ecf20Sopenharmony_ci scmi_sensors = devm_kzalloc(dev, sizeof(*scmi_sensors), GFP_KERNEL); 1818c2ecf20Sopenharmony_ci if (!scmi_sensors) 1828c2ecf20Sopenharmony_ci return -ENOMEM; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci scmi_sensors->handle = handle; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci for (i = 0; i < nr_sensors; i++) { 1878c2ecf20Sopenharmony_ci sensor = handle->sensor_ops->info_get(handle, i); 1888c2ecf20Sopenharmony_ci if (!sensor) 1898c2ecf20Sopenharmony_ci return -EINVAL; 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci switch (sensor->type) { 1928c2ecf20Sopenharmony_ci case TEMPERATURE_C: 1938c2ecf20Sopenharmony_ci case VOLTAGE: 1948c2ecf20Sopenharmony_ci case CURRENT: 1958c2ecf20Sopenharmony_ci case POWER: 1968c2ecf20Sopenharmony_ci case ENERGY: 1978c2ecf20Sopenharmony_ci type = scmi_types[sensor->type]; 1988c2ecf20Sopenharmony_ci if (!nr_count[type]) 1998c2ecf20Sopenharmony_ci nr_types++; 2008c2ecf20Sopenharmony_ci nr_count[type]++; 2018c2ecf20Sopenharmony_ci break; 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci } 2048c2ecf20Sopenharmony_ci 2058c2ecf20Sopenharmony_ci if (nr_count[hwmon_temp]) { 2068c2ecf20Sopenharmony_ci nr_count[hwmon_chip]++; 2078c2ecf20Sopenharmony_ci nr_types++; 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci scmi_hwmon_chan = devm_kcalloc(dev, nr_types, sizeof(*scmi_hwmon_chan), 2118c2ecf20Sopenharmony_ci GFP_KERNEL); 2128c2ecf20Sopenharmony_ci if (!scmi_hwmon_chan) 2138c2ecf20Sopenharmony_ci return -ENOMEM; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci ptr_scmi_ci = devm_kcalloc(dev, nr_types + 1, sizeof(*ptr_scmi_ci), 2168c2ecf20Sopenharmony_ci GFP_KERNEL); 2178c2ecf20Sopenharmony_ci if (!ptr_scmi_ci) 2188c2ecf20Sopenharmony_ci return -ENOMEM; 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci scmi_chip_info.info = ptr_scmi_ci; 2218c2ecf20Sopenharmony_ci chip_info = &scmi_chip_info; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci for (type = 0; type < hwmon_max; type++) { 2248c2ecf20Sopenharmony_ci if (!nr_count[type]) 2258c2ecf20Sopenharmony_ci continue; 2268c2ecf20Sopenharmony_ci 2278c2ecf20Sopenharmony_ci scmi_hwmon_add_chan_info(scmi_hwmon_chan, dev, nr_count[type], 2288c2ecf20Sopenharmony_ci type, hwmon_attributes[type]); 2298c2ecf20Sopenharmony_ci *ptr_scmi_ci++ = scmi_hwmon_chan++; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci scmi_sensors->info[type] = 2328c2ecf20Sopenharmony_ci devm_kcalloc(dev, nr_count[type], 2338c2ecf20Sopenharmony_ci sizeof(*scmi_sensors->info), GFP_KERNEL); 2348c2ecf20Sopenharmony_ci if (!scmi_sensors->info[type]) 2358c2ecf20Sopenharmony_ci return -ENOMEM; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci for (i = nr_sensors - 1; i >= 0 ; i--) { 2398c2ecf20Sopenharmony_ci sensor = handle->sensor_ops->info_get(handle, i); 2408c2ecf20Sopenharmony_ci if (!sensor) 2418c2ecf20Sopenharmony_ci continue; 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci switch (sensor->type) { 2448c2ecf20Sopenharmony_ci case TEMPERATURE_C: 2458c2ecf20Sopenharmony_ci case VOLTAGE: 2468c2ecf20Sopenharmony_ci case CURRENT: 2478c2ecf20Sopenharmony_ci case POWER: 2488c2ecf20Sopenharmony_ci case ENERGY: 2498c2ecf20Sopenharmony_ci type = scmi_types[sensor->type]; 2508c2ecf20Sopenharmony_ci idx = --nr_count[type]; 2518c2ecf20Sopenharmony_ci *(scmi_sensors->info[type] + idx) = sensor; 2528c2ecf20Sopenharmony_ci break; 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci } 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci hwdev = devm_hwmon_device_register_with_info(dev, "scmi_sensors", 2578c2ecf20Sopenharmony_ci scmi_sensors, chip_info, 2588c2ecf20Sopenharmony_ci NULL); 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci return PTR_ERR_OR_ZERO(hwdev); 2618c2ecf20Sopenharmony_ci} 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_cistatic const struct scmi_device_id scmi_id_table[] = { 2648c2ecf20Sopenharmony_ci { SCMI_PROTOCOL_SENSOR, "hwmon" }, 2658c2ecf20Sopenharmony_ci { }, 2668c2ecf20Sopenharmony_ci}; 2678c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(scmi, scmi_id_table); 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic struct scmi_driver scmi_hwmon_drv = { 2708c2ecf20Sopenharmony_ci .name = "scmi-hwmon", 2718c2ecf20Sopenharmony_ci .probe = scmi_hwmon_probe, 2728c2ecf20Sopenharmony_ci .id_table = scmi_id_table, 2738c2ecf20Sopenharmony_ci}; 2748c2ecf20Sopenharmony_cimodule_scmi_driver(scmi_hwmon_drv); 2758c2ecf20Sopenharmony_ci 2768c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sudeep Holla <sudeep.holla@arm.com>"); 2778c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ARM SCMI HWMON interface driver"); 2788c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 279