linux/drivers/thermal/thermal_hwmon.c
<<
>>
Prefs
   1/*
   2 *  thermal_hwmon.c - Generic Thermal Management hwmon support.
   3 *
   4 *  Code based on Intel thermal_core.c. Copyrights of the original code:
   5 *  Copyright (C) 2008 Intel Corp
   6 *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com>
   7 *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
   8 *
   9 *  Copyright (C) 2013 Texas Instruments
  10 *  Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com>
  11 *  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  12 *
  13 *  This program is free software; you can redistribute it and/or modify
  14 *  it under the terms of the GNU General Public License as published by
  15 *  the Free Software Foundation; version 2 of the License.
  16 *
  17 *  This program is distributed in the hope that it will be useful, but
  18 *  WITHOUT ANY WARRANTY; without even the implied warranty of
  19 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  20 *  General Public License for more details.
  21 *
  22 *  You should have received a copy of the GNU General Public License along
  23 *  with this program; if not, write to the Free Software Foundation, Inc.,
  24 *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
  25 *
  26 * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  27 */
  28#include <linux/hwmon.h>
  29#include <linux/thermal.h>
  30#include <linux/slab.h>
  31#include <linux/err.h>
  32#include "thermal_hwmon.h"
  33
  34/* hwmon sys I/F */
  35/* thermal zone devices with the same type share one hwmon device */
  36struct thermal_hwmon_device {
  37        char type[THERMAL_NAME_LENGTH];
  38        struct device *device;
  39        int count;
  40        struct list_head tz_list;
  41        struct list_head node;
  42};
  43
  44struct thermal_hwmon_attr {
  45        struct device_attribute attr;
  46        char name[16];
  47};
  48
  49/* one temperature input for each thermal zone */
  50struct thermal_hwmon_temp {
  51        struct list_head hwmon_node;
  52        struct thermal_zone_device *tz;
  53        struct thermal_hwmon_attr temp_input;   /* hwmon sys attr */
  54        struct thermal_hwmon_attr temp_crit;    /* hwmon sys attr */
  55};
  56
  57static LIST_HEAD(thermal_hwmon_list);
  58
  59static DEFINE_MUTEX(thermal_hwmon_list_lock);
  60
  61static ssize_t
  62name_show(struct device *dev, struct device_attribute *attr, char *buf)
  63{
  64        struct thermal_hwmon_device *hwmon = dev_get_drvdata(dev);
  65        return sprintf(buf, "%s\n", hwmon->type);
  66}
  67static DEVICE_ATTR_RO(name);
  68
  69static ssize_t
  70temp_input_show(struct device *dev, struct device_attribute *attr, char *buf)
  71{
  72        int temperature;
  73        int ret;
  74        struct thermal_hwmon_attr *hwmon_attr
  75                        = container_of(attr, struct thermal_hwmon_attr, attr);
  76        struct thermal_hwmon_temp *temp
  77                        = container_of(hwmon_attr, struct thermal_hwmon_temp,
  78                                       temp_input);
  79        struct thermal_zone_device *tz = temp->tz;
  80
  81        ret = thermal_zone_get_temp(tz, &temperature);
  82
  83        if (ret)
  84                return ret;
  85
  86        return sprintf(buf, "%d\n", temperature);
  87}
  88
  89static ssize_t
  90temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf)
  91{
  92        struct thermal_hwmon_attr *hwmon_attr
  93                        = container_of(attr, struct thermal_hwmon_attr, attr);
  94        struct thermal_hwmon_temp *temp
  95                        = container_of(hwmon_attr, struct thermal_hwmon_temp,
  96                                       temp_crit);
  97        struct thermal_zone_device *tz = temp->tz;
  98        int temperature;
  99        int ret;
 100
 101        ret = tz->ops->get_crit_temp(tz, &temperature);
 102        if (ret)
 103                return ret;
 104
 105        return sprintf(buf, "%d\n", temperature);
 106}
 107
 108
 109static struct thermal_hwmon_device *
 110thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz)
 111{
 112        struct thermal_hwmon_device *hwmon;
 113
 114        mutex_lock(&thermal_hwmon_list_lock);
 115        list_for_each_entry(hwmon, &thermal_hwmon_list, node)
 116                if (!strcmp(hwmon->type, tz->type)) {
 117                        mutex_unlock(&thermal_hwmon_list_lock);
 118                        return hwmon;
 119                }
 120        mutex_unlock(&thermal_hwmon_list_lock);
 121
 122        return NULL;
 123}
 124
 125/* Find the temperature input matching a given thermal zone */
 126static struct thermal_hwmon_temp *
 127thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon,
 128                          const struct thermal_zone_device *tz)
 129{
 130        struct thermal_hwmon_temp *temp;
 131
 132        mutex_lock(&thermal_hwmon_list_lock);
 133        list_for_each_entry(temp, &hwmon->tz_list, hwmon_node)
 134                if (temp->tz == tz) {
 135                        mutex_unlock(&thermal_hwmon_list_lock);
 136                        return temp;
 137                }
 138        mutex_unlock(&thermal_hwmon_list_lock);
 139
 140        return NULL;
 141}
 142
 143static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz)
 144{
 145        int temp;
 146        return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp);
 147}
 148
 149int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz)
 150{
 151        struct thermal_hwmon_device *hwmon;
 152        struct thermal_hwmon_temp *temp;
 153        int new_hwmon_device = 1;
 154        int result;
 155
 156        hwmon = thermal_hwmon_lookup_by_type(tz);
 157        if (hwmon) {
 158                new_hwmon_device = 0;
 159                goto register_sys_interface;
 160        }
 161
 162        hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL);
 163        if (!hwmon)
 164                return -ENOMEM;
 165
 166        INIT_LIST_HEAD(&hwmon->tz_list);
 167        strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH);
 168        hwmon->device = hwmon_device_register(NULL);
 169        if (IS_ERR(hwmon->device)) {
 170                result = PTR_ERR(hwmon->device);
 171                goto free_mem;
 172        }
 173        dev_set_drvdata(hwmon->device, hwmon);
 174        result = device_create_file(hwmon->device, &dev_attr_name);
 175        if (result)
 176                goto free_mem;
 177
 178 register_sys_interface:
 179        temp = kzalloc(sizeof(*temp), GFP_KERNEL);
 180        if (!temp) {
 181                result = -ENOMEM;
 182                goto unregister_name;
 183        }
 184
 185        temp->tz = tz;
 186        hwmon->count++;
 187
 188        snprintf(temp->temp_input.name, sizeof(temp->temp_input.name),
 189                 "temp%d_input", hwmon->count);
 190        temp->temp_input.attr.attr.name = temp->temp_input.name;
 191        temp->temp_input.attr.attr.mode = 0444;
 192        temp->temp_input.attr.show = temp_input_show;
 193        sysfs_attr_init(&temp->temp_input.attr.attr);
 194        result = device_create_file(hwmon->device, &temp->temp_input.attr);
 195        if (result)
 196                goto free_temp_mem;
 197
 198        if (thermal_zone_crit_temp_valid(tz)) {
 199                snprintf(temp->temp_crit.name,
 200                                sizeof(temp->temp_crit.name),
 201                                "temp%d_crit", hwmon->count);
 202                temp->temp_crit.attr.attr.name = temp->temp_crit.name;
 203                temp->temp_crit.attr.attr.mode = 0444;
 204                temp->temp_crit.attr.show = temp_crit_show;
 205                sysfs_attr_init(&temp->temp_crit.attr.attr);
 206                result = device_create_file(hwmon->device,
 207                                            &temp->temp_crit.attr);
 208                if (result)
 209                        goto unregister_input;
 210        }
 211
 212        mutex_lock(&thermal_hwmon_list_lock);
 213        if (new_hwmon_device)
 214                list_add_tail(&hwmon->node, &thermal_hwmon_list);
 215        list_add_tail(&temp->hwmon_node, &hwmon->tz_list);
 216        mutex_unlock(&thermal_hwmon_list_lock);
 217
 218        return 0;
 219
 220 unregister_input:
 221        device_remove_file(hwmon->device, &temp->temp_input.attr);
 222 free_temp_mem:
 223        kfree(temp);
 224 unregister_name:
 225        if (new_hwmon_device) {
 226                device_remove_file(hwmon->device, &dev_attr_name);
 227                hwmon_device_unregister(hwmon->device);
 228        }
 229 free_mem:
 230        if (new_hwmon_device)
 231                kfree(hwmon);
 232
 233        return result;
 234}
 235EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs);
 236
 237void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz)
 238{
 239        struct thermal_hwmon_device *hwmon;
 240        struct thermal_hwmon_temp *temp;
 241
 242        hwmon = thermal_hwmon_lookup_by_type(tz);
 243        if (unlikely(!hwmon)) {
 244                /* Should never happen... */
 245                dev_dbg(&tz->device, "hwmon device lookup failed!\n");
 246                return;
 247        }
 248
 249        temp = thermal_hwmon_lookup_temp(hwmon, tz);
 250        if (unlikely(!temp)) {
 251                /* Should never happen... */
 252                dev_dbg(&tz->device, "temperature input lookup failed!\n");
 253                return;
 254        }
 255
 256        device_remove_file(hwmon->device, &temp->temp_input.attr);
 257        if (thermal_zone_crit_temp_valid(tz))
 258                device_remove_file(hwmon->device, &temp->temp_crit.attr);
 259
 260        mutex_lock(&thermal_hwmon_list_lock);
 261        list_del(&temp->hwmon_node);
 262        kfree(temp);
 263        if (!list_empty(&hwmon->tz_list)) {
 264                mutex_unlock(&thermal_hwmon_list_lock);
 265                return;
 266        }
 267        list_del(&hwmon->node);
 268        mutex_unlock(&thermal_hwmon_list_lock);
 269
 270        device_remove_file(hwmon->device, &dev_attr_name);
 271        hwmon_device_unregister(hwmon->device);
 272        kfree(hwmon);
 273}
 274EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);
 275