Branch data Line data Source code
1 : : // SPDX-License-Identifier: GPL-2.0 2 : : /* 3 : : * thermal_hwmon.c - Generic Thermal Management hwmon support. 4 : : * 5 : : * Code based on Intel thermal_core.c. Copyrights of the original code: 6 : : * Copyright (C) 2008 Intel Corp 7 : : * Copyright (C) 2008 Zhang Rui <rui.zhang@intel.com> 8 : : * Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com> 9 : : * 10 : : * Copyright (C) 2013 Texas Instruments 11 : : * Copyright (C) 2013 Eduardo Valentin <eduardo.valentin@ti.com> 12 : : */ 13 : : #include <linux/hwmon.h> 14 : : #include <linux/thermal.h> 15 : : #include <linux/slab.h> 16 : : #include <linux/err.h> 17 : : #include "thermal_hwmon.h" 18 : : 19 : : /* hwmon sys I/F */ 20 : : /* thermal zone devices with the same type share one hwmon device */ 21 : : struct thermal_hwmon_device { 22 : : char type[THERMAL_NAME_LENGTH]; 23 : : struct device *device; 24 : : int count; 25 : : struct list_head tz_list; 26 : : struct list_head node; 27 : : }; 28 : : 29 : : struct thermal_hwmon_attr { 30 : : struct device_attribute attr; 31 : : char name[16]; 32 : : }; 33 : : 34 : : /* one temperature input for each thermal zone */ 35 : : struct thermal_hwmon_temp { 36 : : struct list_head hwmon_node; 37 : : struct thermal_zone_device *tz; 38 : : struct thermal_hwmon_attr temp_input; /* hwmon sys attr */ 39 : : struct thermal_hwmon_attr temp_crit; /* hwmon sys attr */ 40 : : }; 41 : : 42 : : static LIST_HEAD(thermal_hwmon_list); 43 : : 44 : : static DEFINE_MUTEX(thermal_hwmon_list_lock); 45 : : 46 : : static ssize_t 47 : 0 : temp_input_show(struct device *dev, struct device_attribute *attr, char *buf) 48 : : { 49 : : int temperature; 50 : : int ret; 51 : : struct thermal_hwmon_attr *hwmon_attr 52 : : = container_of(attr, struct thermal_hwmon_attr, attr); 53 : : struct thermal_hwmon_temp *temp 54 : : = container_of(hwmon_attr, struct thermal_hwmon_temp, 55 : : temp_input); 56 : 0 : struct thermal_zone_device *tz = temp->tz; 57 : : 58 : 0 : ret = thermal_zone_get_temp(tz, &temperature); 59 : : 60 : 0 : if (ret) 61 : : return ret; 62 : : 63 : 0 : return sprintf(buf, "%d\n", temperature); 64 : : } 65 : : 66 : : static ssize_t 67 : 0 : temp_crit_show(struct device *dev, struct device_attribute *attr, char *buf) 68 : : { 69 : : struct thermal_hwmon_attr *hwmon_attr 70 : : = container_of(attr, struct thermal_hwmon_attr, attr); 71 : : struct thermal_hwmon_temp *temp 72 : : = container_of(hwmon_attr, struct thermal_hwmon_temp, 73 : : temp_crit); 74 : 0 : struct thermal_zone_device *tz = temp->tz; 75 : : int temperature; 76 : : int ret; 77 : : 78 : 0 : ret = tz->ops->get_crit_temp(tz, &temperature); 79 : 0 : if (ret) 80 : : return ret; 81 : : 82 : 0 : return sprintf(buf, "%d\n", temperature); 83 : : } 84 : : 85 : : 86 : : static struct thermal_hwmon_device * 87 : 2 : thermal_hwmon_lookup_by_type(const struct thermal_zone_device *tz) 88 : : { 89 : : struct thermal_hwmon_device *hwmon; 90 : : char type[THERMAL_NAME_LENGTH]; 91 : : 92 : 2 : mutex_lock(&thermal_hwmon_list_lock); 93 : 2 : list_for_each_entry(hwmon, &thermal_hwmon_list, node) { 94 : 0 : strcpy(type, tz->type); 95 : 0 : strreplace(type, '-', '_'); 96 : 0 : if (!strcmp(hwmon->type, type)) { 97 : 0 : mutex_unlock(&thermal_hwmon_list_lock); 98 : 0 : return hwmon; 99 : : } 100 : : } 101 : 2 : mutex_unlock(&thermal_hwmon_list_lock); 102 : : 103 : 2 : return NULL; 104 : : } 105 : : 106 : : /* Find the temperature input matching a given thermal zone */ 107 : : static struct thermal_hwmon_temp * 108 : 0 : thermal_hwmon_lookup_temp(const struct thermal_hwmon_device *hwmon, 109 : : const struct thermal_zone_device *tz) 110 : : { 111 : : struct thermal_hwmon_temp *temp; 112 : : 113 : 0 : mutex_lock(&thermal_hwmon_list_lock); 114 : 0 : list_for_each_entry(temp, &hwmon->tz_list, hwmon_node) 115 : 0 : if (temp->tz == tz) { 116 : 0 : mutex_unlock(&thermal_hwmon_list_lock); 117 : 0 : return temp; 118 : : } 119 : 0 : mutex_unlock(&thermal_hwmon_list_lock); 120 : : 121 : 0 : return NULL; 122 : : } 123 : : 124 : : static bool thermal_zone_crit_temp_valid(struct thermal_zone_device *tz) 125 : : { 126 : : int temp; 127 : 2 : return tz->ops->get_crit_temp && !tz->ops->get_crit_temp(tz, &temp); 128 : : } 129 : : 130 : 2 : int thermal_add_hwmon_sysfs(struct thermal_zone_device *tz) 131 : : { 132 : : struct thermal_hwmon_device *hwmon; 133 : : struct thermal_hwmon_temp *temp; 134 : : int new_hwmon_device = 1; 135 : : int result; 136 : : 137 : 2 : hwmon = thermal_hwmon_lookup_by_type(tz); 138 : 2 : if (hwmon) { 139 : : new_hwmon_device = 0; 140 : : goto register_sys_interface; 141 : : } 142 : : 143 : 2 : hwmon = kzalloc(sizeof(*hwmon), GFP_KERNEL); 144 : 2 : if (!hwmon) 145 : : return -ENOMEM; 146 : : 147 : 2 : INIT_LIST_HEAD(&hwmon->tz_list); 148 : 2 : strlcpy(hwmon->type, tz->type, THERMAL_NAME_LENGTH); 149 : 2 : strreplace(hwmon->type, '-', '_'); 150 : 2 : hwmon->device = hwmon_device_register_with_info(&tz->device, hwmon->type, 151 : : hwmon, NULL, NULL); 152 : 2 : if (IS_ERR(hwmon->device)) { 153 : : result = PTR_ERR(hwmon->device); 154 : 0 : goto free_mem; 155 : : } 156 : : 157 : : register_sys_interface: 158 : 2 : temp = kzalloc(sizeof(*temp), GFP_KERNEL); 159 : 2 : if (!temp) { 160 : : result = -ENOMEM; 161 : : goto unregister_name; 162 : : } 163 : : 164 : 2 : temp->tz = tz; 165 : 2 : hwmon->count++; 166 : : 167 : 2 : snprintf(temp->temp_input.name, sizeof(temp->temp_input.name), 168 : : "temp%d_input", hwmon->count); 169 : 2 : temp->temp_input.attr.attr.name = temp->temp_input.name; 170 : 2 : temp->temp_input.attr.attr.mode = 0444; 171 : 2 : temp->temp_input.attr.show = temp_input_show; 172 : : sysfs_attr_init(&temp->temp_input.attr.attr); 173 : 2 : result = device_create_file(hwmon->device, &temp->temp_input.attr); 174 : 2 : if (result) 175 : : goto free_temp_mem; 176 : : 177 : 2 : if (thermal_zone_crit_temp_valid(tz)) { 178 : 0 : snprintf(temp->temp_crit.name, 179 : : sizeof(temp->temp_crit.name), 180 : : "temp%d_crit", hwmon->count); 181 : 0 : temp->temp_crit.attr.attr.name = temp->temp_crit.name; 182 : 0 : temp->temp_crit.attr.attr.mode = 0444; 183 : 0 : temp->temp_crit.attr.show = temp_crit_show; 184 : : sysfs_attr_init(&temp->temp_crit.attr.attr); 185 : 0 : result = device_create_file(hwmon->device, 186 : 0 : &temp->temp_crit.attr); 187 : 0 : if (result) 188 : : goto unregister_input; 189 : : } 190 : : 191 : 2 : mutex_lock(&thermal_hwmon_list_lock); 192 : 2 : if (new_hwmon_device) 193 : 2 : list_add_tail(&hwmon->node, &thermal_hwmon_list); 194 : 2 : list_add_tail(&temp->hwmon_node, &hwmon->tz_list); 195 : 2 : mutex_unlock(&thermal_hwmon_list_lock); 196 : : 197 : 2 : return 0; 198 : : 199 : : unregister_input: 200 : 0 : device_remove_file(hwmon->device, &temp->temp_input.attr); 201 : : free_temp_mem: 202 : 0 : kfree(temp); 203 : : unregister_name: 204 : 0 : if (new_hwmon_device) 205 : 0 : hwmon_device_unregister(hwmon->device); 206 : : free_mem: 207 : 0 : if (new_hwmon_device) 208 : 0 : kfree(hwmon); 209 : : 210 : 0 : return result; 211 : : } 212 : : EXPORT_SYMBOL_GPL(thermal_add_hwmon_sysfs); 213 : : 214 : 0 : void thermal_remove_hwmon_sysfs(struct thermal_zone_device *tz) 215 : : { 216 : : struct thermal_hwmon_device *hwmon; 217 : : struct thermal_hwmon_temp *temp; 218 : : 219 : 0 : hwmon = thermal_hwmon_lookup_by_type(tz); 220 : 0 : if (unlikely(!hwmon)) { 221 : : /* Should never happen... */ 222 : : dev_dbg(&tz->device, "hwmon device lookup failed!\n"); 223 : : return; 224 : : } 225 : : 226 : 0 : temp = thermal_hwmon_lookup_temp(hwmon, tz); 227 : 0 : if (unlikely(!temp)) { 228 : : /* Should never happen... */ 229 : : dev_dbg(&tz->device, "temperature input lookup failed!\n"); 230 : : return; 231 : : } 232 : : 233 : 0 : device_remove_file(hwmon->device, &temp->temp_input.attr); 234 : 0 : if (thermal_zone_crit_temp_valid(tz)) 235 : 0 : device_remove_file(hwmon->device, &temp->temp_crit.attr); 236 : : 237 : 0 : mutex_lock(&thermal_hwmon_list_lock); 238 : : list_del(&temp->hwmon_node); 239 : 0 : kfree(temp); 240 : 0 : if (!list_empty(&hwmon->tz_list)) { 241 : 0 : mutex_unlock(&thermal_hwmon_list_lock); 242 : 0 : return; 243 : : } 244 : : list_del(&hwmon->node); 245 : 0 : mutex_unlock(&thermal_hwmon_list_lock); 246 : : 247 : 0 : hwmon_device_unregister(hwmon->device); 248 : 0 : kfree(hwmon); 249 : : } 250 : : EXPORT_SYMBOL_GPL(thermal_remove_hwmon_sysfs);