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