123297edbSGuenter Roeck // SPDX-License-Identifier: GPL-2.0 2ea98b29aSPunit Agrawal /* 3ea98b29aSPunit Agrawal * System Control and Power Interface(SCPI) based hwmon sensor driver 4ea98b29aSPunit Agrawal * 5ea98b29aSPunit Agrawal * Copyright (C) 2015 ARM Ltd. 6ea98b29aSPunit Agrawal * Punit Agrawal <punit.agrawal@arm.com> 7ea98b29aSPunit Agrawal */ 8ea98b29aSPunit Agrawal 9ea98b29aSPunit Agrawal #include <linux/hwmon.h> 10ea98b29aSPunit Agrawal #include <linux/module.h> 11ccc9b826SCarlo Caione #include <linux/of_device.h> 12ea98b29aSPunit Agrawal #include <linux/platform_device.h> 13ea98b29aSPunit Agrawal #include <linux/scpi_protocol.h> 14ea98b29aSPunit Agrawal #include <linux/slab.h> 15ea98b29aSPunit Agrawal #include <linux/sysfs.h> 1668acc77aSPunit Agrawal #include <linux/thermal.h> 17ea98b29aSPunit Agrawal 18ea98b29aSPunit Agrawal struct sensor_data { 19ccc9b826SCarlo Caione unsigned int scale; 20ea98b29aSPunit Agrawal struct scpi_sensor_info info; 21ea98b29aSPunit Agrawal struct device_attribute dev_attr_input; 22ea98b29aSPunit Agrawal struct device_attribute dev_attr_label; 23ea98b29aSPunit Agrawal char input[20]; 24ea98b29aSPunit Agrawal char label[20]; 25ea98b29aSPunit Agrawal }; 26ea98b29aSPunit Agrawal 2768acc77aSPunit Agrawal struct scpi_thermal_zone { 2868acc77aSPunit Agrawal int sensor_id; 2968acc77aSPunit Agrawal struct scpi_sensors *scpi_sensors; 3068acc77aSPunit Agrawal }; 3168acc77aSPunit Agrawal 32ea98b29aSPunit Agrawal struct scpi_sensors { 33ea98b29aSPunit Agrawal struct scpi_ops *scpi_ops; 34ea98b29aSPunit Agrawal struct sensor_data *data; 3568acc77aSPunit Agrawal struct list_head thermal_zones; 36ea98b29aSPunit Agrawal struct attribute **attrs; 37ea98b29aSPunit Agrawal struct attribute_group group; 38ea98b29aSPunit Agrawal const struct attribute_group *groups[2]; 39ea98b29aSPunit Agrawal }; 40ea98b29aSPunit Agrawal 41ccc9b826SCarlo Caione static const u32 gxbb_scpi_scale[] = { 42ccc9b826SCarlo Caione [TEMPERATURE] = 1, /* (celsius) */ 43ccc9b826SCarlo Caione [VOLTAGE] = 1000, /* (millivolts) */ 44ccc9b826SCarlo Caione [CURRENT] = 1000, /* (milliamperes) */ 45ccc9b826SCarlo Caione [POWER] = 1000000, /* (microwatts) */ 46ccc9b826SCarlo Caione [ENERGY] = 1000000, /* (microjoules) */ 47ccc9b826SCarlo Caione }; 48ccc9b826SCarlo Caione 49ccc9b826SCarlo Caione static const u32 scpi_scale[] = { 50ccc9b826SCarlo Caione [TEMPERATURE] = 1000, /* (millicelsius) */ 51ccc9b826SCarlo Caione [VOLTAGE] = 1000, /* (millivolts) */ 52ccc9b826SCarlo Caione [CURRENT] = 1000, /* (milliamperes) */ 53ccc9b826SCarlo Caione [POWER] = 1000000, /* (microwatts) */ 54ccc9b826SCarlo Caione [ENERGY] = 1000000, /* (microjoules) */ 55ccc9b826SCarlo Caione }; 56ccc9b826SCarlo Caione 57ccc9b826SCarlo Caione static void scpi_scale_reading(u64 *value, struct sensor_data *sensor) 58ccc9b826SCarlo Caione { 59ccc9b826SCarlo Caione if (scpi_scale[sensor->info.class] != sensor->scale) { 60ccc9b826SCarlo Caione *value *= scpi_scale[sensor->info.class]; 61ccc9b826SCarlo Caione do_div(*value, sensor->scale); 62ccc9b826SCarlo Caione } 63ccc9b826SCarlo Caione } 64ccc9b826SCarlo Caione 6568acc77aSPunit Agrawal static int scpi_read_temp(void *dev, int *temp) 6668acc77aSPunit Agrawal { 6768acc77aSPunit Agrawal struct scpi_thermal_zone *zone = dev; 6868acc77aSPunit Agrawal struct scpi_sensors *scpi_sensors = zone->scpi_sensors; 6968acc77aSPunit Agrawal struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops; 7068acc77aSPunit Agrawal struct sensor_data *sensor = &scpi_sensors->data[zone->sensor_id]; 712e874159SSudeep Holla u64 value; 7268acc77aSPunit Agrawal int ret; 7368acc77aSPunit Agrawal 7468acc77aSPunit Agrawal ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value); 7568acc77aSPunit Agrawal if (ret) 7668acc77aSPunit Agrawal return ret; 7768acc77aSPunit Agrawal 78ccc9b826SCarlo Caione scpi_scale_reading(&value, sensor); 79ccc9b826SCarlo Caione 8068acc77aSPunit Agrawal *temp = value; 8168acc77aSPunit Agrawal return 0; 8268acc77aSPunit Agrawal } 8368acc77aSPunit Agrawal 84ea98b29aSPunit Agrawal /* hwmon callback functions */ 85ea98b29aSPunit Agrawal static ssize_t 86ea98b29aSPunit Agrawal scpi_show_sensor(struct device *dev, struct device_attribute *attr, char *buf) 87ea98b29aSPunit Agrawal { 88ea98b29aSPunit Agrawal struct scpi_sensors *scpi_sensors = dev_get_drvdata(dev); 89ea98b29aSPunit Agrawal struct scpi_ops *scpi_ops = scpi_sensors->scpi_ops; 90ea98b29aSPunit Agrawal struct sensor_data *sensor; 912e874159SSudeep Holla u64 value; 92ea98b29aSPunit Agrawal int ret; 93ea98b29aSPunit Agrawal 94ea98b29aSPunit Agrawal sensor = container_of(attr, struct sensor_data, dev_attr_input); 95ea98b29aSPunit Agrawal 96ea98b29aSPunit Agrawal ret = scpi_ops->sensor_get_value(sensor->info.sensor_id, &value); 97ea98b29aSPunit Agrawal if (ret) 98ea98b29aSPunit Agrawal return ret; 99ea98b29aSPunit Agrawal 100ccc9b826SCarlo Caione scpi_scale_reading(&value, sensor); 101ccc9b826SCarlo Caione 102*78d13552SRiwen Lu /* 103*78d13552SRiwen Lu * Temperature sensor values are treated as signed values based on 104*78d13552SRiwen Lu * observation even though that is not explicitly specified, and 105*78d13552SRiwen Lu * because an unsigned u64 temperature does not really make practical 106*78d13552SRiwen Lu * sense especially when the temperature is below zero degrees Celsius. 107*78d13552SRiwen Lu */ 108*78d13552SRiwen Lu if (sensor->info.class == TEMPERATURE) 109*78d13552SRiwen Lu return sprintf(buf, "%lld\n", (s64)value); 110*78d13552SRiwen Lu 1112e874159SSudeep Holla return sprintf(buf, "%llu\n", value); 112ea98b29aSPunit Agrawal } 113ea98b29aSPunit Agrawal 114ea98b29aSPunit Agrawal static ssize_t 115ea98b29aSPunit Agrawal scpi_show_label(struct device *dev, struct device_attribute *attr, char *buf) 116ea98b29aSPunit Agrawal { 117ea98b29aSPunit Agrawal struct sensor_data *sensor; 118ea98b29aSPunit Agrawal 119ea98b29aSPunit Agrawal sensor = container_of(attr, struct sensor_data, dev_attr_label); 120ea98b29aSPunit Agrawal 121ea98b29aSPunit Agrawal return sprintf(buf, "%s\n", sensor->info.name); 122ea98b29aSPunit Agrawal } 123ea98b29aSPunit Agrawal 124778fb961SJulia Lawall static const struct thermal_zone_of_device_ops scpi_sensor_ops = { 12568acc77aSPunit Agrawal .get_temp = scpi_read_temp, 12668acc77aSPunit Agrawal }; 12768acc77aSPunit Agrawal 128ccc9b826SCarlo Caione static const struct of_device_id scpi_of_match[] = { 129ccc9b826SCarlo Caione {.compatible = "arm,scpi-sensors", .data = &scpi_scale}, 130ccc9b826SCarlo Caione {.compatible = "amlogic,meson-gxbb-scpi-sensors", .data = &gxbb_scpi_scale}, 131ccc9b826SCarlo Caione {}, 132ccc9b826SCarlo Caione }; 133ccc9b826SCarlo Caione MODULE_DEVICE_TABLE(of, scpi_of_match); 134ccc9b826SCarlo Caione 135ea98b29aSPunit Agrawal static int scpi_hwmon_probe(struct platform_device *pdev) 136ea98b29aSPunit Agrawal { 137ea98b29aSPunit Agrawal u16 nr_sensors, i; 138ccc9b826SCarlo Caione const u32 *scale; 139ea98b29aSPunit Agrawal int num_temp = 0, num_volt = 0, num_current = 0, num_power = 0; 140fb3b07efSSudeep Holla int num_energy = 0; 141ea98b29aSPunit Agrawal struct scpi_ops *scpi_ops; 142ea98b29aSPunit Agrawal struct device *hwdev, *dev = &pdev->dev; 143ea98b29aSPunit Agrawal struct scpi_sensors *scpi_sensors; 144ccc9b826SCarlo Caione const struct of_device_id *of_id; 145d7817ff2SEduardo Valentin int idx, ret; 146ea98b29aSPunit Agrawal 147ea98b29aSPunit Agrawal scpi_ops = get_scpi_ops(); 148ea98b29aSPunit Agrawal if (!scpi_ops) 149ea98b29aSPunit Agrawal return -EPROBE_DEFER; 150ea98b29aSPunit Agrawal 151ea98b29aSPunit Agrawal ret = scpi_ops->sensor_get_capability(&nr_sensors); 152ea98b29aSPunit Agrawal if (ret) 153ea98b29aSPunit Agrawal return ret; 154ea98b29aSPunit Agrawal 155ea98b29aSPunit Agrawal if (!nr_sensors) 156ea98b29aSPunit Agrawal return -ENODEV; 157ea98b29aSPunit Agrawal 158ea98b29aSPunit Agrawal scpi_sensors = devm_kzalloc(dev, sizeof(*scpi_sensors), GFP_KERNEL); 159ea98b29aSPunit Agrawal if (!scpi_sensors) 160ea98b29aSPunit Agrawal return -ENOMEM; 161ea98b29aSPunit Agrawal 162ea98b29aSPunit Agrawal scpi_sensors->data = devm_kcalloc(dev, nr_sensors, 163ea98b29aSPunit Agrawal sizeof(*scpi_sensors->data), GFP_KERNEL); 164ea98b29aSPunit Agrawal if (!scpi_sensors->data) 165ea98b29aSPunit Agrawal return -ENOMEM; 166ea98b29aSPunit Agrawal 167ea98b29aSPunit Agrawal scpi_sensors->attrs = devm_kcalloc(dev, (nr_sensors * 2) + 1, 168ea98b29aSPunit Agrawal sizeof(*scpi_sensors->attrs), GFP_KERNEL); 169ea98b29aSPunit Agrawal if (!scpi_sensors->attrs) 170ea98b29aSPunit Agrawal return -ENOMEM; 171ea98b29aSPunit Agrawal 172ea98b29aSPunit Agrawal scpi_sensors->scpi_ops = scpi_ops; 173ea98b29aSPunit Agrawal 174ccc9b826SCarlo Caione of_id = of_match_device(scpi_of_match, &pdev->dev); 175ccc9b826SCarlo Caione if (!of_id) { 176ccc9b826SCarlo Caione dev_err(&pdev->dev, "Unable to initialize scpi-hwmon data\n"); 177ccc9b826SCarlo Caione return -ENODEV; 178ccc9b826SCarlo Caione } 179ccc9b826SCarlo Caione scale = of_id->data; 180ccc9b826SCarlo Caione 181cef03d7eSSudeep Holla for (i = 0, idx = 0; i < nr_sensors; i++) { 182cef03d7eSSudeep Holla struct sensor_data *sensor = &scpi_sensors->data[idx]; 183ea98b29aSPunit Agrawal 184ea98b29aSPunit Agrawal ret = scpi_ops->sensor_get_info(i, &sensor->info); 185ea98b29aSPunit Agrawal if (ret) 186ea98b29aSPunit Agrawal return ret; 187ea98b29aSPunit Agrawal 188ea98b29aSPunit Agrawal switch (sensor->info.class) { 189ea98b29aSPunit Agrawal case TEMPERATURE: 190ea98b29aSPunit Agrawal snprintf(sensor->input, sizeof(sensor->input), 191ea98b29aSPunit Agrawal "temp%d_input", num_temp + 1); 192ea98b29aSPunit Agrawal snprintf(sensor->label, sizeof(sensor->input), 193ea98b29aSPunit Agrawal "temp%d_label", num_temp + 1); 194ea98b29aSPunit Agrawal num_temp++; 195ea98b29aSPunit Agrawal break; 196ea98b29aSPunit Agrawal case VOLTAGE: 197ea98b29aSPunit Agrawal snprintf(sensor->input, sizeof(sensor->input), 198ea98b29aSPunit Agrawal "in%d_input", num_volt); 199ea98b29aSPunit Agrawal snprintf(sensor->label, sizeof(sensor->input), 200ea98b29aSPunit Agrawal "in%d_label", num_volt); 201ea98b29aSPunit Agrawal num_volt++; 202ea98b29aSPunit Agrawal break; 203ea98b29aSPunit Agrawal case CURRENT: 204ea98b29aSPunit Agrawal snprintf(sensor->input, sizeof(sensor->input), 205ea98b29aSPunit Agrawal "curr%d_input", num_current + 1); 206ea98b29aSPunit Agrawal snprintf(sensor->label, sizeof(sensor->input), 207ea98b29aSPunit Agrawal "curr%d_label", num_current + 1); 208ea98b29aSPunit Agrawal num_current++; 209ea98b29aSPunit Agrawal break; 210ea98b29aSPunit Agrawal case POWER: 211ea98b29aSPunit Agrawal snprintf(sensor->input, sizeof(sensor->input), 212ea98b29aSPunit Agrawal "power%d_input", num_power + 1); 213ea98b29aSPunit Agrawal snprintf(sensor->label, sizeof(sensor->input), 214ea98b29aSPunit Agrawal "power%d_label", num_power + 1); 215ea98b29aSPunit Agrawal num_power++; 216ea98b29aSPunit Agrawal break; 217fb3b07efSSudeep Holla case ENERGY: 218fb3b07efSSudeep Holla snprintf(sensor->input, sizeof(sensor->input), 219fb3b07efSSudeep Holla "energy%d_input", num_energy + 1); 220fb3b07efSSudeep Holla snprintf(sensor->label, sizeof(sensor->input), 221fb3b07efSSudeep Holla "energy%d_label", num_energy + 1); 222fb3b07efSSudeep Holla num_energy++; 223fb3b07efSSudeep Holla break; 224ea98b29aSPunit Agrawal default: 225cef03d7eSSudeep Holla continue; 226ea98b29aSPunit Agrawal } 227ea98b29aSPunit Agrawal 228ccc9b826SCarlo Caione sensor->scale = scale[sensor->info.class]; 229ccc9b826SCarlo Caione 2306a0785aaSGuenter Roeck sensor->dev_attr_input.attr.mode = 0444; 231ea98b29aSPunit Agrawal sensor->dev_attr_input.show = scpi_show_sensor; 232ea98b29aSPunit Agrawal sensor->dev_attr_input.attr.name = sensor->input; 233ea98b29aSPunit Agrawal 2346a0785aaSGuenter Roeck sensor->dev_attr_label.attr.mode = 0444; 235ea98b29aSPunit Agrawal sensor->dev_attr_label.show = scpi_show_label; 236ea98b29aSPunit Agrawal sensor->dev_attr_label.attr.name = sensor->label; 237ea98b29aSPunit Agrawal 238cef03d7eSSudeep Holla scpi_sensors->attrs[idx << 1] = &sensor->dev_attr_input.attr; 239cef03d7eSSudeep Holla scpi_sensors->attrs[(idx << 1) + 1] = &sensor->dev_attr_label.attr; 240ea98b29aSPunit Agrawal 241cef03d7eSSudeep Holla sysfs_attr_init(scpi_sensors->attrs[idx << 1]); 242cef03d7eSSudeep Holla sysfs_attr_init(scpi_sensors->attrs[(idx << 1) + 1]); 243cef03d7eSSudeep Holla idx++; 244ea98b29aSPunit Agrawal } 245ea98b29aSPunit Agrawal 246ea98b29aSPunit Agrawal scpi_sensors->group.attrs = scpi_sensors->attrs; 247ea98b29aSPunit Agrawal scpi_sensors->groups[0] = &scpi_sensors->group; 248ea98b29aSPunit Agrawal 24968acc77aSPunit Agrawal platform_set_drvdata(pdev, scpi_sensors); 25068acc77aSPunit Agrawal 251ea98b29aSPunit Agrawal hwdev = devm_hwmon_device_register_with_groups(dev, 252ea98b29aSPunit Agrawal "scpi_sensors", scpi_sensors, scpi_sensors->groups); 253ea98b29aSPunit Agrawal 25468acc77aSPunit Agrawal if (IS_ERR(hwdev)) 25568acc77aSPunit Agrawal return PTR_ERR(hwdev); 25668acc77aSPunit Agrawal 25768acc77aSPunit Agrawal /* 25868acc77aSPunit Agrawal * Register the temperature sensors with the thermal framework 25968acc77aSPunit Agrawal * to allow their usage in setting up the thermal zones from 26068acc77aSPunit Agrawal * device tree. 26168acc77aSPunit Agrawal * 26268acc77aSPunit Agrawal * NOTE: Not all temperature sensors maybe used for thermal 26368acc77aSPunit Agrawal * control 26468acc77aSPunit Agrawal */ 26568acc77aSPunit Agrawal INIT_LIST_HEAD(&scpi_sensors->thermal_zones); 26668acc77aSPunit Agrawal for (i = 0; i < nr_sensors; i++) { 26768acc77aSPunit Agrawal struct sensor_data *sensor = &scpi_sensors->data[i]; 268d7817ff2SEduardo Valentin struct thermal_zone_device *z; 26968acc77aSPunit Agrawal struct scpi_thermal_zone *zone; 27068acc77aSPunit Agrawal 27168acc77aSPunit Agrawal if (sensor->info.class != TEMPERATURE) 27268acc77aSPunit Agrawal continue; 27368acc77aSPunit Agrawal 27468acc77aSPunit Agrawal zone = devm_kzalloc(dev, sizeof(*zone), GFP_KERNEL); 275d7817ff2SEduardo Valentin if (!zone) 276d7817ff2SEduardo Valentin return -ENOMEM; 27768acc77aSPunit Agrawal 27868acc77aSPunit Agrawal zone->sensor_id = i; 27968acc77aSPunit Agrawal zone->scpi_sensors = scpi_sensors; 280d7817ff2SEduardo Valentin z = devm_thermal_zone_of_sensor_register(dev, 281d7817ff2SEduardo Valentin sensor->info.sensor_id, 282d7817ff2SEduardo Valentin zone, 283d7817ff2SEduardo Valentin &scpi_sensor_ops); 28468acc77aSPunit Agrawal /* 28568acc77aSPunit Agrawal * The call to thermal_zone_of_sensor_register returns 28668acc77aSPunit Agrawal * an error for sensors that are not associated with 28768acc77aSPunit Agrawal * any thermal zones or if the thermal subsystem is 28868acc77aSPunit Agrawal * not configured. 28968acc77aSPunit Agrawal */ 2903045b5d6Szhong jiang if (IS_ERR(z)) 29168acc77aSPunit Agrawal devm_kfree(dev, zone); 29268acc77aSPunit Agrawal } 29368acc77aSPunit Agrawal 29468acc77aSPunit Agrawal return 0; 295ea98b29aSPunit Agrawal } 296ea98b29aSPunit Agrawal 297ea98b29aSPunit Agrawal static struct platform_driver scpi_hwmon_platdrv = { 298ea98b29aSPunit Agrawal .driver = { 299ea98b29aSPunit Agrawal .name = "scpi-hwmon", 300ea98b29aSPunit Agrawal .of_match_table = scpi_of_match, 301ea98b29aSPunit Agrawal }, 302ea98b29aSPunit Agrawal .probe = scpi_hwmon_probe, 303ea98b29aSPunit Agrawal }; 304ea98b29aSPunit Agrawal module_platform_driver(scpi_hwmon_platdrv); 305ea98b29aSPunit Agrawal 306ea98b29aSPunit Agrawal MODULE_AUTHOR("Punit Agrawal <punit.agrawal@arm.com>"); 307ea98b29aSPunit Agrawal MODULE_DESCRIPTION("ARM SCPI HWMON interface driver"); 308ea98b29aSPunit Agrawal MODULE_LICENSE("GPL v2"); 309