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 = thermal_zone_device_priv(zone); 18 unsigned long long tmp; 19 acpi_status status; 20 21 status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp); 22 if (ACPI_FAILURE(status)) 23 return -EIO; 24 25 if (d->lpat_table) { 26 int conv_temp; 27 28 conv_temp = acpi_lpat_raw_to_temp(d->lpat_table, (int)tmp); 29 if (conv_temp < 0) 30 return conv_temp; 31 32 *temp = conv_temp * 10; 33 } else { 34 /* _TMP returns the temperature in tenths of degrees Kelvin */ 35 *temp = deci_kelvin_to_millicelsius(tmp); 36 } 37 38 return 0; 39 } 40 41 static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone, 42 int trip, int temp) 43 { 44 struct int34x_thermal_zone *d = thermal_zone_device_priv(zone); 45 char name[] = {'P', 'A', 'T', '0' + trip, '\0'}; 46 acpi_status status; 47 48 if (trip > 9) 49 return -EINVAL; 50 51 status = acpi_execute_simple_method(d->adev->handle, name, 52 millicelsius_to_deci_kelvin(temp)); 53 if (ACPI_FAILURE(status)) 54 return -EIO; 55 56 return 0; 57 } 58 59 static void int340x_thermal_critical(struct thermal_zone_device *zone) 60 { 61 dev_dbg(&zone->device, "%s: critical temperature reached\n", zone->type); 62 } 63 64 static struct thermal_zone_device_ops int340x_thermal_zone_ops = { 65 .get_temp = int340x_thermal_get_zone_temp, 66 .set_trip_temp = int340x_thermal_set_trip_temp, 67 .critical = int340x_thermal_critical, 68 }; 69 70 static inline void *int_to_trip_priv(int i) 71 { 72 return (void *)(long)i; 73 } 74 75 static inline int trip_priv_to_int(const struct thermal_trip *trip) 76 { 77 return (long)trip->priv; 78 } 79 80 static int int340x_thermal_read_trips(struct acpi_device *zone_adev, 81 struct thermal_trip *zone_trips, 82 int trip_cnt) 83 { 84 int i, ret; 85 86 ret = thermal_acpi_critical_trip_temp(zone_adev, 87 &zone_trips[trip_cnt].temperature); 88 if (!ret) { 89 zone_trips[trip_cnt].type = THERMAL_TRIP_CRITICAL; 90 trip_cnt++; 91 } 92 93 ret = thermal_acpi_hot_trip_temp(zone_adev, 94 &zone_trips[trip_cnt].temperature); 95 if (!ret) { 96 zone_trips[trip_cnt].type = THERMAL_TRIP_HOT; 97 trip_cnt++; 98 } 99 100 ret = thermal_acpi_passive_trip_temp(zone_adev, 101 &zone_trips[trip_cnt].temperature); 102 if (!ret) { 103 zone_trips[trip_cnt].type = THERMAL_TRIP_PASSIVE; 104 trip_cnt++; 105 } 106 107 for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { 108 ret = thermal_acpi_active_trip_temp(zone_adev, i, 109 &zone_trips[trip_cnt].temperature); 110 if (ret) 111 break; 112 113 zone_trips[trip_cnt].type = THERMAL_TRIP_ACTIVE; 114 zone_trips[trip_cnt].priv = int_to_trip_priv(i); 115 trip_cnt++; 116 } 117 118 return trip_cnt; 119 } 120 121 static struct thermal_zone_params int340x_thermal_params = { 122 .governor_name = "user_space", 123 .no_hwmon = true, 124 }; 125 126 struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev, 127 int (*get_temp) (struct thermal_zone_device *, int *)) 128 { 129 struct int34x_thermal_zone *int34x_zone; 130 struct thermal_trip *zone_trips; 131 unsigned long long trip_cnt = 0; 132 unsigned long long hyst; 133 int trip_mask = 0; 134 acpi_status status; 135 int i, ret; 136 137 int34x_zone = kzalloc(sizeof(*int34x_zone), GFP_KERNEL); 138 if (!int34x_zone) 139 return ERR_PTR(-ENOMEM); 140 141 int34x_zone->adev = adev; 142 143 int34x_zone->ops = kmemdup(&int340x_thermal_zone_ops, 144 sizeof(int340x_thermal_zone_ops), GFP_KERNEL); 145 if (!int34x_zone->ops) { 146 ret = -ENOMEM; 147 goto err_ops_alloc; 148 } 149 150 if (get_temp) 151 int34x_zone->ops->get_temp = get_temp; 152 153 status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt); 154 if (ACPI_SUCCESS(status)) { 155 int34x_zone->aux_trip_nr = trip_cnt; 156 trip_mask = BIT(trip_cnt) - 1; 157 } 158 159 zone_trips = kzalloc(sizeof(*zone_trips) * (trip_cnt + INT340X_THERMAL_MAX_TRIP_COUNT), 160 GFP_KERNEL); 161 if (!zone_trips) { 162 ret = -ENOMEM; 163 goto err_trips_alloc; 164 } 165 166 for (i = 0; i < trip_cnt; i++) { 167 zone_trips[i].type = THERMAL_TRIP_PASSIVE; 168 zone_trips[i].temperature = THERMAL_TEMP_INVALID; 169 } 170 171 trip_cnt = int340x_thermal_read_trips(adev, zone_trips, trip_cnt); 172 173 status = acpi_evaluate_integer(adev->handle, "GTSH", NULL, &hyst); 174 if (ACPI_SUCCESS(status)) 175 hyst *= 100; 176 else 177 hyst = 0; 178 179 for (i = 0; i < trip_cnt; ++i) 180 zone_trips[i].hysteresis = hyst; 181 182 int34x_zone->trips = zone_trips; 183 184 int34x_zone->lpat_table = acpi_lpat_get_conversion_table(adev->handle); 185 186 int34x_zone->zone = thermal_zone_device_register_with_trips( 187 acpi_device_bid(adev), 188 zone_trips, trip_cnt, 189 trip_mask, int34x_zone, 190 int34x_zone->ops, 191 &int340x_thermal_params, 192 0, 0); 193 if (IS_ERR(int34x_zone->zone)) { 194 ret = PTR_ERR(int34x_zone->zone); 195 goto err_thermal_zone; 196 } 197 ret = thermal_zone_device_enable(int34x_zone->zone); 198 if (ret) 199 goto err_enable; 200 201 return int34x_zone; 202 203 err_enable: 204 thermal_zone_device_unregister(int34x_zone->zone); 205 err_thermal_zone: 206 kfree(int34x_zone->trips); 207 acpi_lpat_free_conversion_table(int34x_zone->lpat_table); 208 err_trips_alloc: 209 kfree(int34x_zone->ops); 210 err_ops_alloc: 211 kfree(int34x_zone); 212 return ERR_PTR(ret); 213 } 214 EXPORT_SYMBOL_GPL(int340x_thermal_zone_add); 215 216 void int340x_thermal_zone_remove(struct int34x_thermal_zone *int34x_zone) 217 { 218 thermal_zone_device_unregister(int34x_zone->zone); 219 acpi_lpat_free_conversion_table(int34x_zone->lpat_table); 220 kfree(int34x_zone->trips); 221 kfree(int34x_zone->ops); 222 kfree(int34x_zone); 223 } 224 EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove); 225 226 static int int340x_update_one_trip(struct thermal_trip *trip, void *arg) 227 { 228 struct acpi_device *zone_adev = arg; 229 int temp, err; 230 231 switch (trip->type) { 232 case THERMAL_TRIP_CRITICAL: 233 err = thermal_acpi_critical_trip_temp(zone_adev, &temp); 234 break; 235 case THERMAL_TRIP_HOT: 236 err = thermal_acpi_hot_trip_temp(zone_adev, &temp); 237 break; 238 case THERMAL_TRIP_PASSIVE: 239 err = thermal_acpi_passive_trip_temp(zone_adev, &temp); 240 break; 241 case THERMAL_TRIP_ACTIVE: 242 err = thermal_acpi_active_trip_temp(zone_adev, 243 trip_priv_to_int(trip), 244 &temp); 245 break; 246 default: 247 err = -ENODEV; 248 } 249 if (err) 250 temp = THERMAL_TEMP_INVALID; 251 252 trip->temperature = temp; 253 return 0; 254 } 255 256 void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone) 257 { 258 thermal_zone_for_each_trip(int34x_zone->zone, int340x_update_one_trip, 259 int34x_zone->adev); 260 } 261 EXPORT_SYMBOL_GPL(int340x_thermal_update_trips); 262 263 MODULE_AUTHOR("Aaron Lu <aaron.lu@intel.com>"); 264 MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 265 MODULE_DESCRIPTION("Intel INT340x common thermal zone handler"); 266 MODULE_LICENSE("GPL v2"); 267