1/* 2 * IBM PowerNV platform sensors for temperature/fan/voltage/power 3 * Copyright (C) 2014 IBM 4 * 5 * This program is free software; you can redistribute it and/or modify 6 * it under the terms of the GNU General Public License as published by 7 * the Free Software Foundation; either version 2 of the License, or 8 * (at your option) any later version. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. 17 */ 18 19#define DRVNAME "ibmpowernv" 20#define pr_fmt(fmt) DRVNAME ": " fmt 21 22#include <linux/init.h> 23#include <linux/module.h> 24#include <linux/kernel.h> 25#include <linux/hwmon.h> 26#include <linux/hwmon-sysfs.h> 27#include <linux/of.h> 28#include <linux/slab.h> 29 30#include <linux/platform_device.h> 31#include <asm/opal.h> 32#include <linux/err.h> 33 34#define MAX_ATTR_LEN 32 35 36/* Sensor suffix name from DT */ 37#define DT_FAULT_ATTR_SUFFIX "faulted" 38#define DT_DATA_ATTR_SUFFIX "data" 39#define DT_THRESHOLD_ATTR_SUFFIX "thrs" 40 41/* 42 * Enumerates all the types of sensors in the POWERNV platform and does index 43 * into 'struct sensor_group' 44 */ 45enum sensors { 46 FAN, 47 AMBIENT_TEMP, 48 POWER_SUPPLY, 49 POWER_INPUT, 50 MAX_SENSOR_TYPE, 51}; 52 53static struct sensor_group { 54 const char *name; 55 const char *compatible; 56 struct attribute_group group; 57 u32 attr_count; 58} sensor_groups[] = { 59 {"fan", "ibm,opal-sensor-cooling-fan"}, 60 {"temp", "ibm,opal-sensor-amb-temp"}, 61 {"in", "ibm,opal-sensor-power-supply"}, 62 {"power", "ibm,opal-sensor-power"} 63}; 64 65struct sensor_data { 66 u32 id; /* An opaque id of the firmware for each sensor */ 67 enum sensors type; 68 char name[MAX_ATTR_LEN]; 69 struct device_attribute dev_attr; 70}; 71 72struct platform_data { 73 const struct attribute_group *attr_groups[MAX_SENSOR_TYPE + 1]; 74 u32 sensors_count; /* Total count of sensors from each group */ 75}; 76 77/* Platform device representing all the ibmpowernv sensors */ 78static struct platform_device *pdevice; 79 80static ssize_t show_sensor(struct device *dev, struct device_attribute *devattr, 81 char *buf) 82{ 83 struct sensor_data *sdata = container_of(devattr, struct sensor_data, 84 dev_attr); 85 ssize_t ret; 86 u32 x; 87 88 ret = opal_get_sensor_data(sdata->id, &x); 89 if (ret) 90 return ret; 91 92 /* Convert temperature to milli-degrees */ 93 if (sdata->type == AMBIENT_TEMP) 94 x *= 1000; 95 /* Convert power to micro-watts */ 96 else if (sdata->type == POWER_INPUT) 97 x *= 1000000; 98 99 return sprintf(buf, "%u\n", x); 100} 101 102static int __init get_sensor_index_attr(const char *name, u32 *index, 103 char *attr) 104{ 105 char *hash_pos = strchr(name, '#'); 106 char buf[8] = { 0 }; 107 char *dash_pos; 108 u32 copy_len; 109 int err; 110 111 if (!hash_pos) 112 return -EINVAL; 113 114 dash_pos = strchr(hash_pos, '-'); 115 if (!dash_pos) 116 return -EINVAL; 117 118 copy_len = dash_pos - hash_pos - 1; 119 if (copy_len >= sizeof(buf)) 120 return -EINVAL; 121 122 strncpy(buf, hash_pos + 1, copy_len); 123 124 err = kstrtou32(buf, 10, index); 125 if (err) 126 return err; 127 128 strncpy(attr, dash_pos + 1, MAX_ATTR_LEN); 129 130 return 0; 131} 132 133/* 134 * This function translates the DT node name into the 'hwmon' attribute name. 135 * IBMPOWERNV device node appear like cooling-fan#2-data, amb-temp#1-thrs etc. 136 * which need to be mapped as fan2_input, temp1_max respectively before 137 * populating them inside hwmon device class. 138 */ 139static int __init create_hwmon_attr_name(struct device *dev, enum sensors type, 140 const char *node_name, 141 char *hwmon_attr_name) 142{ 143 char attr_suffix[MAX_ATTR_LEN]; 144 char *attr_name; 145 u32 index; 146 int err; 147 148 err = get_sensor_index_attr(node_name, &index, attr_suffix); 149 if (err) { 150 dev_err(dev, "Sensor device node name '%s' is invalid\n", 151 node_name); 152 return err; 153 } 154 155 if (!strcmp(attr_suffix, DT_FAULT_ATTR_SUFFIX)) { 156 attr_name = "fault"; 157 } else if (!strcmp(attr_suffix, DT_DATA_ATTR_SUFFIX)) { 158 attr_name = "input"; 159 } else if (!strcmp(attr_suffix, DT_THRESHOLD_ATTR_SUFFIX)) { 160 if (type == AMBIENT_TEMP) 161 attr_name = "max"; 162 else if (type == FAN) 163 attr_name = "min"; 164 else 165 return -ENOENT; 166 } else { 167 return -ENOENT; 168 } 169 170 snprintf(hwmon_attr_name, MAX_ATTR_LEN, "%s%d_%s", 171 sensor_groups[type].name, index, attr_name); 172 return 0; 173} 174 175static int __init populate_attr_groups(struct platform_device *pdev) 176{ 177 struct platform_data *pdata = platform_get_drvdata(pdev); 178 const struct attribute_group **pgroups = pdata->attr_groups; 179 struct device_node *opal, *np; 180 enum sensors type; 181 182 opal = of_find_node_by_path("/ibm,opal/sensors"); 183 if (!opal) { 184 dev_dbg(&pdev->dev, "Opal node 'sensors' not found\n"); 185 return -ENODEV; 186 } 187 188 for_each_child_of_node(opal, np) { 189 if (np->name == NULL) 190 continue; 191 192 for (type = 0; type < MAX_SENSOR_TYPE; type++) 193 if (of_device_is_compatible(np, 194 sensor_groups[type].compatible)) { 195 sensor_groups[type].attr_count++; 196 break; 197 } 198 } 199 200 of_node_put(opal); 201 202 for (type = 0; type < MAX_SENSOR_TYPE; type++) { 203 sensor_groups[type].group.attrs = devm_kzalloc(&pdev->dev, 204 sizeof(struct attribute *) * 205 (sensor_groups[type].attr_count + 1), 206 GFP_KERNEL); 207 if (!sensor_groups[type].group.attrs) 208 return -ENOMEM; 209 210 pgroups[type] = &sensor_groups[type].group; 211 pdata->sensors_count += sensor_groups[type].attr_count; 212 sensor_groups[type].attr_count = 0; 213 } 214 215 return 0; 216} 217 218/* 219 * Iterate through the device tree for each child of 'sensors' node, create 220 * a sysfs attribute file, the file is named by translating the DT node name 221 * to the name required by the higher 'hwmon' driver like fan1_input, temp1_max 222 * etc.. 223 */ 224static int __init create_device_attrs(struct platform_device *pdev) 225{ 226 struct platform_data *pdata = platform_get_drvdata(pdev); 227 const struct attribute_group **pgroups = pdata->attr_groups; 228 struct device_node *opal, *np; 229 struct sensor_data *sdata; 230 u32 sensor_id; 231 enum sensors type; 232 u32 count = 0; 233 int err = 0; 234 235 opal = of_find_node_by_path("/ibm,opal/sensors"); 236 sdata = devm_kzalloc(&pdev->dev, pdata->sensors_count * sizeof(*sdata), 237 GFP_KERNEL); 238 if (!sdata) { 239 err = -ENOMEM; 240 goto exit_put_node; 241 } 242 243 for_each_child_of_node(opal, np) { 244 if (np->name == NULL) 245 continue; 246 247 for (type = 0; type < MAX_SENSOR_TYPE; type++) 248 if (of_device_is_compatible(np, 249 sensor_groups[type].compatible)) 250 break; 251 252 if (type == MAX_SENSOR_TYPE) 253 continue; 254 255 if (of_property_read_u32(np, "sensor-id", &sensor_id)) { 256 dev_info(&pdev->dev, 257 "'sensor-id' missing in the node '%s'\n", 258 np->name); 259 continue; 260 } 261 262 sdata[count].id = sensor_id; 263 sdata[count].type = type; 264 err = create_hwmon_attr_name(&pdev->dev, type, np->name, 265 sdata[count].name); 266 if (err) 267 goto exit_put_node; 268 269 sysfs_attr_init(&sdata[count].dev_attr.attr); 270 sdata[count].dev_attr.attr.name = sdata[count].name; 271 sdata[count].dev_attr.attr.mode = S_IRUGO; 272 sdata[count].dev_attr.show = show_sensor; 273 274 pgroups[type]->attrs[sensor_groups[type].attr_count++] = 275 &sdata[count++].dev_attr.attr; 276 } 277 278exit_put_node: 279 of_node_put(opal); 280 return err; 281} 282 283static int __init ibmpowernv_probe(struct platform_device *pdev) 284{ 285 struct platform_data *pdata; 286 struct device *hwmon_dev; 287 int err; 288 289 pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); 290 if (!pdata) 291 return -ENOMEM; 292 293 platform_set_drvdata(pdev, pdata); 294 pdata->sensors_count = 0; 295 err = populate_attr_groups(pdev); 296 if (err) 297 return err; 298 299 /* Create sysfs attribute data for each sensor found in the DT */ 300 err = create_device_attrs(pdev); 301 if (err) 302 return err; 303 304 /* Finally, register with hwmon */ 305 hwmon_dev = devm_hwmon_device_register_with_groups(&pdev->dev, DRVNAME, 306 pdata, 307 pdata->attr_groups); 308 309 return PTR_ERR_OR_ZERO(hwmon_dev); 310} 311 312static struct platform_driver ibmpowernv_driver = { 313 .driver = { 314 .owner = THIS_MODULE, 315 .name = DRVNAME, 316 }, 317}; 318 319static int __init ibmpowernv_init(void) 320{ 321 int err; 322 323 pdevice = platform_device_alloc(DRVNAME, 0); 324 if (!pdevice) { 325 pr_err("Device allocation failed\n"); 326 err = -ENOMEM; 327 goto exit; 328 } 329 330 err = platform_device_add(pdevice); 331 if (err) { 332 pr_err("Device addition failed (%d)\n", err); 333 goto exit_device_put; 334 } 335 336 err = platform_driver_probe(&ibmpowernv_driver, ibmpowernv_probe); 337 if (err) { 338 if (err != -ENODEV) 339 pr_err("Platform driver probe failed (%d)\n", err); 340 341 goto exit_device_del; 342 } 343 344 return 0; 345 346exit_device_del: 347 platform_device_del(pdevice); 348exit_device_put: 349 platform_device_put(pdevice); 350exit: 351 return err; 352} 353 354static void __exit ibmpowernv_exit(void) 355{ 356 platform_driver_unregister(&ibmpowernv_driver); 357 platform_device_unregister(pdevice); 358} 359 360MODULE_AUTHOR("Neelesh Gupta <neelegup@linux.vnet.ibm.com>"); 361MODULE_DESCRIPTION("IBM POWERNV platform sensors"); 362MODULE_LICENSE("GPL"); 363 364module_init(ibmpowernv_init); 365module_exit(ibmpowernv_exit); 366