1 /* 2 * int340x_thermal_zone.c 3 * Copyright (c) 2015, Intel Corporation. 4 * 5 * This program is free software; you can redistribute it and/or modify it 6 * under the terms and conditions of the GNU General Public License, 7 * version 2, as published by the Free Software Foundation. 8 * 9 * This program is distributed in the hope it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 12 * more details. 13 * 14 */ 15 #include <linux/kernel.h> 16 #include <linux/module.h> 17 #include <linux/init.h> 18 #include <linux/acpi.h> 19 #include <linux/thermal.h> 20 #include "int340x_thermal_zone.h" 21 22 static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone, 23 int *temp) 24 { 25 struct int34x_thermal_zone *d = zone->devdata; 26 unsigned long long tmp; 27 acpi_status status; 28 29 if (d->override_ops && d->override_ops->get_temp) 30 return d->override_ops->get_temp(zone, temp); 31 32 status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp); 33 if (ACPI_FAILURE(status)) 34 return -EIO; 35 36 if (d->lpat_table) { 37 int conv_temp; 38 39 conv_temp = acpi_lpat_raw_to_temp(d->lpat_table, (int)tmp); 40 if (conv_temp < 0) 41 return conv_temp; 42 43 *temp = (unsigned long)conv_temp * 10; 44 } else 45 /* _TMP returns the temperature in tenths of degrees Kelvin */ 46 *temp = DECI_KELVIN_TO_MILLICELSIUS(tmp); 47 48 return 0; 49 } 50 51 static int int340x_thermal_get_trip_temp(struct thermal_zone_device *zone, 52 int trip, int *temp) 53 { 54 struct int34x_thermal_zone *d = zone->devdata; 55 int i; 56 57 if (d->override_ops && d->override_ops->get_trip_temp) 58 return d->override_ops->get_trip_temp(zone, trip, temp); 59 60 if (trip < d->aux_trip_nr) 61 *temp = d->aux_trips[trip]; 62 else if (trip == d->crt_trip_id) 63 *temp = d->crt_temp; 64 else if (trip == d->psv_trip_id) 65 *temp = d->psv_temp; 66 else if (trip == d->hot_trip_id) 67 *temp = d->hot_temp; 68 else { 69 for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { 70 if (d->act_trips[i].valid && 71 d->act_trips[i].id == trip) { 72 *temp = d->act_trips[i].temp; 73 break; 74 } 75 } 76 if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT) 77 return -EINVAL; 78 } 79 80 return 0; 81 } 82 83 static int int340x_thermal_get_trip_type(struct thermal_zone_device *zone, 84 int trip, 85 enum thermal_trip_type *type) 86 { 87 struct int34x_thermal_zone *d = zone->devdata; 88 int i; 89 90 if (d->override_ops && d->override_ops->get_trip_type) 91 return d->override_ops->get_trip_type(zone, trip, type); 92 93 if (trip < d->aux_trip_nr) 94 *type = THERMAL_TRIP_PASSIVE; 95 else if (trip == d->crt_trip_id) 96 *type = THERMAL_TRIP_CRITICAL; 97 else if (trip == d->hot_trip_id) 98 *type = THERMAL_TRIP_HOT; 99 else if (trip == d->psv_trip_id) 100 *type = THERMAL_TRIP_PASSIVE; 101 else { 102 for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { 103 if (d->act_trips[i].valid && 104 d->act_trips[i].id == trip) { 105 *type = THERMAL_TRIP_ACTIVE; 106 break; 107 } 108 } 109 if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT) 110 return -EINVAL; 111 } 112 113 return 0; 114 } 115 116 static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone, 117 int trip, int temp) 118 { 119 struct int34x_thermal_zone *d = zone->devdata; 120 acpi_status status; 121 char name[10]; 122 123 if (d->override_ops && d->override_ops->set_trip_temp) 124 return d->override_ops->set_trip_temp(zone, trip, temp); 125 126 snprintf(name, sizeof(name), "PAT%d", trip); 127 status = acpi_execute_simple_method(d->adev->handle, name, 128 MILLICELSIUS_TO_DECI_KELVIN(temp)); 129 if (ACPI_FAILURE(status)) 130 return -EIO; 131 132 d->aux_trips[trip] = temp; 133 134 return 0; 135 } 136 137 138 static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone, 139 int trip, int *temp) 140 { 141 struct int34x_thermal_zone *d = zone->devdata; 142 acpi_status status; 143 unsigned long long hyst; 144 145 if (d->override_ops && d->override_ops->get_trip_hyst) 146 return d->override_ops->get_trip_hyst(zone, trip, temp); 147 148 status = acpi_evaluate_integer(d->adev->handle, "GTSH", NULL, &hyst); 149 if (ACPI_FAILURE(status)) 150 *temp = 0; 151 else 152 *temp = hyst * 100; 153 154 return 0; 155 } 156 157 static struct thermal_zone_device_ops int340x_thermal_zone_ops = { 158 .get_temp = int340x_thermal_get_zone_temp, 159 .get_trip_temp = int340x_thermal_get_trip_temp, 160 .get_trip_type = int340x_thermal_get_trip_type, 161 .set_trip_temp = int340x_thermal_set_trip_temp, 162 .get_trip_hyst = int340x_thermal_get_trip_hyst, 163 }; 164 165 static int int340x_thermal_get_trip_config(acpi_handle handle, char *name, 166 int *temp) 167 { 168 unsigned long long r; 169 acpi_status status; 170 171 status = acpi_evaluate_integer(handle, name, NULL, &r); 172 if (ACPI_FAILURE(status)) 173 return -EIO; 174 175 *temp = DECI_KELVIN_TO_MILLICELSIUS(r); 176 177 return 0; 178 } 179 180 int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone) 181 { 182 int trip_cnt = int34x_zone->aux_trip_nr; 183 int i; 184 185 int34x_zone->crt_trip_id = -1; 186 if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_CRT", 187 &int34x_zone->crt_temp)) 188 int34x_zone->crt_trip_id = trip_cnt++; 189 190 int34x_zone->hot_trip_id = -1; 191 if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_HOT", 192 &int34x_zone->hot_temp)) 193 int34x_zone->hot_trip_id = trip_cnt++; 194 195 int34x_zone->psv_trip_id = -1; 196 if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_PSV", 197 &int34x_zone->psv_temp)) 198 int34x_zone->psv_trip_id = trip_cnt++; 199 200 for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { 201 char name[5] = { '_', 'A', 'C', '0' + i, '\0' }; 202 203 if (int340x_thermal_get_trip_config(int34x_zone->adev->handle, 204 name, 205 &int34x_zone->act_trips[i].temp)) 206 break; 207 208 int34x_zone->act_trips[i].id = trip_cnt++; 209 int34x_zone->act_trips[i].valid = true; 210 } 211 212 return trip_cnt; 213 } 214 EXPORT_SYMBOL_GPL(int340x_thermal_read_trips); 215 216 static struct thermal_zone_params int340x_thermal_params = { 217 .governor_name = "user_space", 218 .no_hwmon = true, 219 }; 220 221 struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev, 222 struct thermal_zone_device_ops *override_ops) 223 { 224 struct int34x_thermal_zone *int34x_thermal_zone; 225 acpi_status status; 226 unsigned long long trip_cnt; 227 int trip_mask = 0; 228 int ret; 229 230 int34x_thermal_zone = kzalloc(sizeof(*int34x_thermal_zone), 231 GFP_KERNEL); 232 if (!int34x_thermal_zone) 233 return ERR_PTR(-ENOMEM); 234 235 int34x_thermal_zone->adev = adev; 236 int34x_thermal_zone->override_ops = override_ops; 237 238 status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt); 239 if (ACPI_FAILURE(status)) 240 trip_cnt = 0; 241 else { 242 int34x_thermal_zone->aux_trips = 243 kcalloc(trip_cnt, 244 sizeof(*int34x_thermal_zone->aux_trips), 245 GFP_KERNEL); 246 if (!int34x_thermal_zone->aux_trips) { 247 ret = -ENOMEM; 248 goto err_trip_alloc; 249 } 250 trip_mask = BIT(trip_cnt) - 1; 251 int34x_thermal_zone->aux_trip_nr = trip_cnt; 252 } 253 254 trip_cnt = int340x_thermal_read_trips(int34x_thermal_zone); 255 256 int34x_thermal_zone->lpat_table = acpi_lpat_get_conversion_table( 257 adev->handle); 258 259 int34x_thermal_zone->zone = thermal_zone_device_register( 260 acpi_device_bid(adev), 261 trip_cnt, 262 trip_mask, int34x_thermal_zone, 263 &int340x_thermal_zone_ops, 264 &int340x_thermal_params, 265 0, 0); 266 if (IS_ERR(int34x_thermal_zone->zone)) { 267 ret = PTR_ERR(int34x_thermal_zone->zone); 268 goto err_thermal_zone; 269 } 270 271 return int34x_thermal_zone; 272 273 err_thermal_zone: 274 acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table); 275 kfree(int34x_thermal_zone->aux_trips); 276 err_trip_alloc: 277 kfree(int34x_thermal_zone); 278 return ERR_PTR(ret); 279 } 280 EXPORT_SYMBOL_GPL(int340x_thermal_zone_add); 281 282 void int340x_thermal_zone_remove(struct int34x_thermal_zone 283 *int34x_thermal_zone) 284 { 285 thermal_zone_device_unregister(int34x_thermal_zone->zone); 286 acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table); 287 kfree(int34x_thermal_zone->aux_trips); 288 kfree(int34x_thermal_zone); 289 } 290 EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove); 291 292 MODULE_AUTHOR("Aaron Lu <aaron.lu@intel.com>"); 293 MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 294 MODULE_DESCRIPTION("Intel INT340x common thermal zone handler"); 295 MODULE_LICENSE("GPL v2"); 296