18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * System Control and Power Interface(SCPI) based hwmon sensor driver 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright (C) 2015 ARM Ltd. 68c2ecf20Sopenharmony_ci * Punit Agrawal <punit.agrawal@arm.com> 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/hwmon.h> 108c2ecf20Sopenharmony_ci#include <linux/module.h> 118c2ecf20Sopenharmony_ci#include <linux/of_device.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci#include <linux/scpi_protocol.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/sysfs.h> 168c2ecf20Sopenharmony_ci#include <linux/thermal.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistruct sensor_data { 198c2ecf20Sopenharmony_ci unsigned int scale; 208c2ecf20Sopenharmony_ci struct scpi_sensor_info info; 218c2ecf20Sopenharmony_ci struct device_attribute dev_attr_input; 228c2ecf20Sopenharmony_ci struct device_attribute dev_attr_label; 238c2ecf20Sopenharmony_ci char input[20]; 248c2ecf20Sopenharmony_ci char label[20]; 258c2ecf20Sopenharmony_ci}; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistruct scpi_thermal_zone { 288c2ecf20Sopenharmony_ci int sensor_id; 298c2ecf20Sopenharmony_ci struct scpi_sensors *scpi_sensors; 308c2ecf20Sopenharmony_ci}; 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistruct scpi_sensors { 338c2ecf20Sopenharmony_ci struct scpi_ops *scpi_ops; 348c2ecf20Sopenharmony_ci struct sensor_data *data; 358c2ecf20Sopenharmony_ci struct list_head thermal_zones; 368c2ecf20Sopenharmony_ci struct attribute **attrs; 378c2ecf20Sopenharmony_ci struct attribute_group group; 388c2ecf20Sopenharmony_ci const struct attribute_group *groups[2]; 398c2ecf20Sopenharmony_ci}; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_cistatic const u32 gxbb_scpi_scale[] = { 428c2ecf20Sopenharmony_ci [TEMPERATURE] = 1, /* (celsius) */ 438c2ecf20Sopenharmony_ci [VOLTAGE] = 1000, /* (millivolts) */ 448c2ecf20Sopenharmony_ci [CURRENT] = 1000, /* (milliamperes) */ 458c2ecf20Sopenharmony_ci [POWER] = 1000000, /* (microwatts) */ 468c2ecf20Sopenharmony_ci [ENERGY] = 1000000, /* (microjoules) */ 478c2ecf20Sopenharmony_ci}; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_cistatic const u32 scpi_scale[] = { 508c2ecf20Sopenharmony_ci [TEMPERATURE] = 1000, /* (millicelsius) */ 518c2ecf20Sopenharmony_ci [VOLTAGE] = 1000, /* (millivolts) */ 528c2ecf20Sopenharmony_ci [CURRENT] = 1000, /* (milliamperes) */ 538c2ecf20Sopenharmony_ci [POWER] = 1000000, /* (microwatts) */ 548c2ecf20Sopenharmony_ci [ENERGY] = 1000000, /* (microjoules) */ 558c2ecf20Sopenharmony_ci}; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_cistatic void scpi_scale_reading(u64 *value, struct sensor_data *sensor) 588c2ecf20Sopenharmony_ci{ 598c2ecf20Sopenharmony_ci if (scpi_scale[sensor->info.class] != sensor->scale) { 608c2ecf20Sopenharmony_ci *value *= scpi_scale[sensor->info.class]; 618c2ecf20Sopenharmony_ci do_div(*value, sensor->scale); 628c2ecf20Sopenharmony_ci } 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int scpi_read_temp(void *dev, int *temp) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci struct scpi_thermal_zone *zone = dev; 688c2ecf20Sopenharmony_ci struct scpi_sensors *scpi_sensors = zone->scpi_sensors; 698c2ecf20Sopenharmony_ci struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops; 708c2ecf20Sopenharmony_ci struct sensor_data *sensor = &scpi_sensors->data[zone->sensor_id]; 718c2ecf20Sopenharmony_ci u64 value; 728c2ecf20Sopenharmony_ci int ret; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value); 758c2ecf20Sopenharmony_ci if (ret) 768c2ecf20Sopenharmony_ci return ret; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci scpi_scale_reading(&value, sensor); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci *temp = value; 818c2ecf20Sopenharmony_ci return 0; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci/* hwmon callback functions */ 858c2ecf20Sopenharmony_cistatic ssize_t 868c2ecf20Sopenharmony_ciscpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci struct scpi_sensors *scpi_sensors = dev_get_drvdata(dev); 898c2ecf20Sopenharmony_ci struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops; 908c2ecf20Sopenharmony_ci struct sensor_data *sensor; 918c2ecf20Sopenharmony_ci u64 value; 928c2ecf20Sopenharmony_ci int ret; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci sensor = container_of(attr, struct sensor_data, dev_attr_input); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value); 978c2ecf20Sopenharmony_ci if (ret) 988c2ecf20Sopenharmony_ci return ret; 998c2ecf20Sopenharmony_ci 1008c2ecf20Sopenharmony_ci scpi_scale_reading(&value, sensor); 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* 1038c2ecf20Sopenharmony_ci * Temperature sensor values are treated as signed values based on 1048c2ecf20Sopenharmony_ci * observation even though that is not explicitly specified, and 1058c2ecf20Sopenharmony_ci * because an unsigned u64 temperature does not really make practical 1068c2ecf20Sopenharmony_ci * sense especially when the temperature is below zero degrees Celsius. 1078c2ecf20Sopenharmony_ci */ 1088c2ecf20Sopenharmony_ci if (sensor->info.class == TEMPERATURE) 1098c2ecf20Sopenharmony_ci return sprintf(buf, "%lld\n", (s64)value); 1108c2ecf20Sopenharmony_ci 1118c2ecf20Sopenharmony_ci return sprintf(buf, "%llu\n", value); 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic ssize_t 1158c2ecf20Sopenharmony_ciscpi_show_label(struct device *dev, struct device_attribute *attr, char *buf) 1168c2ecf20Sopenharmony_ci{ 1178c2ecf20Sopenharmony_ci struct sensor_data *sensor; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci sensor = container_of(attr, struct sensor_data, dev_attr_label); 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci return sprintf(buf, "%s\n", sensor->info.name); 1228c2ecf20Sopenharmony_ci} 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_cistatic const struct thermal_zone_of_device_ops scpi_sensor_ops = { 1258c2ecf20Sopenharmony_ci .get_temp = scpi_read_temp, 1268c2ecf20Sopenharmony_ci}; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic const struct of_device_id scpi_of_match[] = { 1298c2ecf20Sopenharmony_ci {.compatible = "arm,scpi-sensors", .data = &scpi_scale}, 1308c2ecf20Sopenharmony_ci {.compatible = "amlogic,meson-gxbb-scpi-sensors", .data = &gxbb_scpi_scale}, 1318c2ecf20Sopenharmony_ci {}, 1328c2ecf20Sopenharmony_ci}; 1338c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, scpi_of_match); 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_cistatic int scpi_hwmon_probe(struct platform_device *pdev) 1368c2ecf20Sopenharmony_ci{ 1378c2ecf20Sopenharmony_ci u16 nr_sensors, i; 1388c2ecf20Sopenharmony_ci const u32 *scale; 1398c2ecf20Sopenharmony_ci int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0; 1408c2ecf20Sopenharmony_ci int num_energy = 0; 1418c2ecf20Sopenharmony_ci struct scpi_ops *scpi_ops; 1428c2ecf20Sopenharmony_ci struct device *hwdev, *dev = &pdev->dev; 1438c2ecf20Sopenharmony_ci struct scpi_sensors *scpi_sensors; 1448c2ecf20Sopenharmony_ci const struct of_device_id *of_id; 1458c2ecf20Sopenharmony_ci int idx, ret; 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_ci scpi_ops = get_scpi_ops(); 1488c2ecf20Sopenharmony_ci if (!scpi_ops) 1498c2ecf20Sopenharmony_ci return -EPROBE_DEFER; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci ret = scpi_ops->sensor_get_capability(&nr_sensors); 1528c2ecf20Sopenharmony_ci if (ret) 1538c2ecf20Sopenharmony_ci return ret; 1548c2ecf20Sopenharmony_ci 1558c2ecf20Sopenharmony_ci if (!nr_sensors) 1568c2ecf20Sopenharmony_ci return -ENODEV; 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci scpi_sensors = devm_kzalloc(dev, sizeof(*scpi_sensors), GFP_KERNEL); 1598c2ecf20Sopenharmony_ci if (!scpi_sensors) 1608c2ecf20Sopenharmony_ci return -ENOMEM; 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_ci scpi_sensors->data = devm_kcalloc(dev, nr_sensors, 1638c2ecf20Sopenharmony_ci sizeof(*scpi_sensors->data), GFP_KERNEL); 1648c2ecf20Sopenharmony_ci if (!scpi_sensors->data) 1658c2ecf20Sopenharmony_ci return -ENOMEM; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci scpi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1, 1688c2ecf20Sopenharmony_ci sizeof(*scpi_sensors->attrs), GFP_KERNEL); 1698c2ecf20Sopenharmony_ci if (!scpi_sensors->attrs) 1708c2ecf20Sopenharmony_ci return -ENOMEM; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci scpi_sensors->scpi_ops = scpi_ops; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci of_id = of_match_device(scpi_of_match, &pdev->dev); 1758c2ecf20Sopenharmony_ci if (!of_id) { 1768c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unable to initialize scpi-hwmon data\n"); 1778c2ecf20Sopenharmony_ci return -ENODEV; 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci scale = of_id->data; 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci for (i = 0, idx = 0; i < nr_sensors; i++) { 1828c2ecf20Sopenharmony_ci struct sensor_data *sensor = &scpi_sensors->data[idx]; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci ret = scpi_ops->sensor_get_info(i, &sensor->info); 1858c2ecf20Sopenharmony_ci if (ret) 1868c2ecf20Sopenharmony_ci return ret; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci switch (sensor->info.class) { 1898c2ecf20Sopenharmony_ci case TEMPERATURE: 1908c2ecf20Sopenharmony_ci snprintf(sensor->input, sizeof(sensor->input), 1918c2ecf20Sopenharmony_ci "temp%d_input", num_temp + 1); 1928c2ecf20Sopenharmony_ci snprintf(sensor->label, sizeof(sensor->input), 1938c2ecf20Sopenharmony_ci "temp%d_label", num_temp + 1); 1948c2ecf20Sopenharmony_ci num_temp++; 1958c2ecf20Sopenharmony_ci break; 1968c2ecf20Sopenharmony_ci case VOLTAGE: 1978c2ecf20Sopenharmony_ci snprintf(sensor->input, sizeof(sensor->input), 1988c2ecf20Sopenharmony_ci "in%d_input", num_volt); 1998c2ecf20Sopenharmony_ci snprintf(sensor->label, sizeof(sensor->input), 2008c2ecf20Sopenharmony_ci "in%d_label", num_volt); 2018c2ecf20Sopenharmony_ci num_volt++; 2028c2ecf20Sopenharmony_ci break; 2038c2ecf20Sopenharmony_ci case CURRENT: 2048c2ecf20Sopenharmony_ci snprintf(sensor->input, sizeof(sensor->input), 2058c2ecf20Sopenharmony_ci "curr%d_input", num_current + 1); 2068c2ecf20Sopenharmony_ci snprintf(sensor->label, sizeof(sensor->input), 2078c2ecf20Sopenharmony_ci "curr%d_label", num_current + 1); 2088c2ecf20Sopenharmony_ci num_current++; 2098c2ecf20Sopenharmony_ci break; 2108c2ecf20Sopenharmony_ci case POWER: 2118c2ecf20Sopenharmony_ci snprintf(sensor->input, sizeof(sensor->input), 2128c2ecf20Sopenharmony_ci "power%d_input", num_power + 1); 2138c2ecf20Sopenharmony_ci snprintf(sensor->label, sizeof(sensor->input), 2148c2ecf20Sopenharmony_ci "power%d_label", num_power + 1); 2158c2ecf20Sopenharmony_ci num_power++; 2168c2ecf20Sopenharmony_ci break; 2178c2ecf20Sopenharmony_ci case ENERGY: 2188c2ecf20Sopenharmony_ci snprintf(sensor->input, sizeof(sensor->input), 2198c2ecf20Sopenharmony_ci "energy%d_input", num_energy + 1); 2208c2ecf20Sopenharmony_ci snprintf(sensor->label, sizeof(sensor->input), 2218c2ecf20Sopenharmony_ci "energy%d_label", num_energy + 1); 2228c2ecf20Sopenharmony_ci num_energy++; 2238c2ecf20Sopenharmony_ci break; 2248c2ecf20Sopenharmony_ci default: 2258c2ecf20Sopenharmony_ci continue; 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ci sensor->scale = scale[sensor->info.class]; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci sensor->dev_attr_input.attr.mode = 0444; 2318c2ecf20Sopenharmony_ci sensor->dev_attr_input.show = scpi_show_sensor; 2328c2ecf20Sopenharmony_ci sensor->dev_attr_input.attr.name = sensor->input; 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci sensor->dev_attr_label.attr.mode = 0444; 2358c2ecf20Sopenharmony_ci sensor->dev_attr_label.show = scpi_show_label; 2368c2ecf20Sopenharmony_ci sensor->dev_attr_label.attr.name = sensor->label; 2378c2ecf20Sopenharmony_ci 2388c2ecf20Sopenharmony_ci scpi_sensors->attrs[idx << 1] = &sensor->dev_attr_input.attr; 2398c2ecf20Sopenharmony_ci scpi_sensors->attrs[(idx << 1) + 1] = &sensor->dev_attr_label.attr; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci sysfs_attr_init(scpi_sensors->attrs[idx << 1]); 2428c2ecf20Sopenharmony_ci sysfs_attr_init(scpi_sensors->attrs[(idx << 1) + 1]); 2438c2ecf20Sopenharmony_ci idx++; 2448c2ecf20Sopenharmony_ci } 2458c2ecf20Sopenharmony_ci 2468c2ecf20Sopenharmony_ci scpi_sensors->group.attrs = scpi_sensors->attrs; 2478c2ecf20Sopenharmony_ci scpi_sensors->groups[0] = &scpi_sensors->group; 2488c2ecf20Sopenharmony_ci 2498c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, scpi_sensors); 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci hwdev = devm_hwmon_device_register_with_groups(dev, 2528c2ecf20Sopenharmony_ci "scpi_sensors", scpi_sensors, scpi_sensors->groups); 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci if (IS_ERR(hwdev)) 2558c2ecf20Sopenharmony_ci return PTR_ERR(hwdev); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci /* 2588c2ecf20Sopenharmony_ci * Register the temperature sensors with the thermal framework 2598c2ecf20Sopenharmony_ci * to allow their usage in setting up the thermal zones from 2608c2ecf20Sopenharmony_ci * device tree. 2618c2ecf20Sopenharmony_ci * 2628c2ecf20Sopenharmony_ci * NOTE: Not all temperature sensors maybe used for thermal 2638c2ecf20Sopenharmony_ci * control 2648c2ecf20Sopenharmony_ci */ 2658c2ecf20Sopenharmony_ci INIT_LIST_HEAD(&scpi_sensors->thermal_zones); 2668c2ecf20Sopenharmony_ci for (i = 0; i < nr_sensors; i++) { 2678c2ecf20Sopenharmony_ci struct sensor_data *sensor = &scpi_sensors->data[i]; 2688c2ecf20Sopenharmony_ci struct thermal_zone_device *z; 2698c2ecf20Sopenharmony_ci struct scpi_thermal_zone *zone; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci if (sensor->info.class != TEMPERATURE) 2728c2ecf20Sopenharmony_ci continue; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL); 2758c2ecf20Sopenharmony_ci if (!zone) 2768c2ecf20Sopenharmony_ci return -ENOMEM; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci zone->sensor_id = i; 2798c2ecf20Sopenharmony_ci zone->scpi_sensors = scpi_sensors; 2808c2ecf20Sopenharmony_ci z = devm_thermal_zone_of_sensor_register(dev, 2818c2ecf20Sopenharmony_ci sensor->info.sensor_id, 2828c2ecf20Sopenharmony_ci zone, 2838c2ecf20Sopenharmony_ci &scpi_sensor_ops); 2848c2ecf20Sopenharmony_ci /* 2858c2ecf20Sopenharmony_ci * The call to thermal_zone_of_sensor_register returns 2868c2ecf20Sopenharmony_ci * an error for sensors that are not associated with 2878c2ecf20Sopenharmony_ci * any thermal zones or if the thermal subsystem is 2888c2ecf20Sopenharmony_ci * not configured. 2898c2ecf20Sopenharmony_ci */ 2908c2ecf20Sopenharmony_ci if (IS_ERR(z)) 2918c2ecf20Sopenharmony_ci devm_kfree(dev, zone); 2928c2ecf20Sopenharmony_ci } 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci return 0; 2958c2ecf20Sopenharmony_ci} 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_cistatic struct platform_driver scpi_hwmon_platdrv = { 2988c2ecf20Sopenharmony_ci .driver = { 2998c2ecf20Sopenharmony_ci .name = "scpi-hwmon", 3008c2ecf20Sopenharmony_ci .of_match_table = scpi_of_match, 3018c2ecf20Sopenharmony_ci }, 3028c2ecf20Sopenharmony_ci .probe = scpi_hwmon_probe, 3038c2ecf20Sopenharmony_ci}; 3048c2ecf20Sopenharmony_cimodule_platform_driver(scpi_hwmon_platdrv); 3058c2ecf20Sopenharmony_ci 3068c2ecf20Sopenharmony_ciMODULE_AUTHOR("Punit Agrawal <punit.agrawal@arm.com>"); 3078c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ARM SCPI HWMON interface driver"); 3088c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 309