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(thermal_zone_device(zone), "%s: critical temperature reached\n", 62 thermal_zone_device_type(zone)); 63 } 64 65 static inline void *int_to_trip_priv(int i) 66 { 67 return (void *)(long)i; 68 } 69 70 static inline int trip_priv_to_int(const struct thermal_trip *trip) 71 { 72 return (long)trip->priv; 73 } 74 75 static int int340x_thermal_read_trips(struct acpi_device *zone_adev, 76 struct thermal_trip *zone_trips, 77 int trip_cnt) 78 { 79 int i, ret; 80 81 ret = thermal_acpi_critical_trip_temp(zone_adev, 82 &zone_trips[trip_cnt].temperature); 83 if (!ret) { 84 zone_trips[trip_cnt].type = THERMAL_TRIP_CRITICAL; 85 trip_cnt++; 86 } 87 88 ret = thermal_acpi_hot_trip_temp(zone_adev, 89 &zone_trips[trip_cnt].temperature); 90 if (!ret) { 91 zone_trips[trip_cnt].type = THERMAL_TRIP_HOT; 92 trip_cnt++; 93 } 94 95 ret = thermal_acpi_passive_trip_temp(zone_adev, 96 &zone_trips[trip_cnt].temperature); 97 if (!ret) { 98 zone_trips[trip_cnt].type = THERMAL_TRIP_PASSIVE; 99 trip_cnt++; 100 } 101 102 for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) { 103 ret = thermal_acpi_active_trip_temp(zone_adev, i, 104 &zone_trips[trip_cnt].temperature); 105 if (ret) 106 break; 107 108 zone_trips[trip_cnt].type = THERMAL_TRIP_ACTIVE; 109 zone_trips[trip_cnt].priv = int_to_trip_priv(i); 110 trip_cnt++; 111 } 112 113 return trip_cnt; 114 } 115 116 static struct thermal_zone_params int340x_thermal_params = { 117 .governor_name = "user_space", 118 .no_hwmon = true, 119 }; 120 121 struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev, 122 int (*get_temp) (struct thermal_zone_device *, int *)) 123 { 124 const struct thermal_zone_device_ops zone_ops = { 125 .set_trip_temp = int340x_thermal_set_trip_temp, 126 .critical = int340x_thermal_critical, 127 .get_temp = get_temp ? get_temp : int340x_thermal_get_zone_temp, 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 acpi_status status; 134 int i, ret; 135 136 int34x_zone = kzalloc(sizeof(*int34x_zone), GFP_KERNEL); 137 if (!int34x_zone) 138 return ERR_PTR(-ENOMEM); 139 140 int34x_zone->adev = adev; 141 142 status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt); 143 if (ACPI_SUCCESS(status)) 144 int34x_zone->aux_trip_nr = trip_cnt; 145 146 zone_trips = kzalloc(sizeof(*zone_trips) * (trip_cnt + INT340X_THERMAL_MAX_TRIP_COUNT), 147 GFP_KERNEL); 148 if (!zone_trips) { 149 ret = -ENOMEM; 150 goto err_trips_alloc; 151 } 152 153 for (i = 0; i < trip_cnt; i++) { 154 zone_trips[i].type = THERMAL_TRIP_PASSIVE; 155 zone_trips[i].temperature = THERMAL_TEMP_INVALID; 156 zone_trips[i].flags |= THERMAL_TRIP_FLAG_RW_TEMP; 157 } 158 159 trip_cnt = int340x_thermal_read_trips(adev, zone_trips, trip_cnt); 160 161 status = acpi_evaluate_integer(adev->handle, "GTSH", NULL, &hyst); 162 if (ACPI_SUCCESS(status)) 163 hyst *= 100; 164 else 165 hyst = 0; 166 167 for (i = 0; i < trip_cnt; ++i) 168 zone_trips[i].hysteresis = hyst; 169 170 int34x_zone->lpat_table = acpi_lpat_get_conversion_table(adev->handle); 171 172 int34x_zone->zone = thermal_zone_device_register_with_trips( 173 acpi_device_bid(adev), 174 zone_trips, trip_cnt, 175 int34x_zone, 176 &zone_ops, 177 &int340x_thermal_params, 178 0, 0); 179 kfree(zone_trips); 180 181 if (IS_ERR(int34x_zone->zone)) { 182 ret = PTR_ERR(int34x_zone->zone); 183 goto err_thermal_zone; 184 } 185 ret = thermal_zone_device_enable(int34x_zone->zone); 186 if (ret) 187 goto err_enable; 188 189 return int34x_zone; 190 191 err_enable: 192 thermal_zone_device_unregister(int34x_zone->zone); 193 err_thermal_zone: 194 acpi_lpat_free_conversion_table(int34x_zone->lpat_table); 195 err_trips_alloc: 196 kfree(int34x_zone); 197 return ERR_PTR(ret); 198 } 199 EXPORT_SYMBOL_GPL(int340x_thermal_zone_add); 200 201 void int340x_thermal_zone_remove(struct int34x_thermal_zone *int34x_zone) 202 { 203 thermal_zone_device_unregister(int34x_zone->zone); 204 acpi_lpat_free_conversion_table(int34x_zone->lpat_table); 205 kfree(int34x_zone); 206 } 207 EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove); 208 209 static int int340x_update_one_trip(struct thermal_trip *trip, void *arg) 210 { 211 struct int34x_thermal_zone *int34x_zone = arg; 212 struct acpi_device *zone_adev = int34x_zone->adev; 213 int temp, err; 214 215 switch (trip->type) { 216 case THERMAL_TRIP_CRITICAL: 217 err = thermal_acpi_critical_trip_temp(zone_adev, &temp); 218 break; 219 case THERMAL_TRIP_HOT: 220 err = thermal_acpi_hot_trip_temp(zone_adev, &temp); 221 break; 222 case THERMAL_TRIP_PASSIVE: 223 err = thermal_acpi_passive_trip_temp(zone_adev, &temp); 224 break; 225 case THERMAL_TRIP_ACTIVE: 226 err = thermal_acpi_active_trip_temp(zone_adev, 227 trip_priv_to_int(trip), 228 &temp); 229 break; 230 default: 231 err = -ENODEV; 232 } 233 if (err) 234 temp = THERMAL_TEMP_INVALID; 235 236 thermal_zone_set_trip_temp(int34x_zone->zone, trip, temp); 237 238 return 0; 239 } 240 241 void int340x_thermal_update_trips(struct int34x_thermal_zone *int34x_zone) 242 { 243 thermal_zone_for_each_trip(int34x_zone->zone, int340x_update_one_trip, 244 int34x_zone); 245 } 246 EXPORT_SYMBOL_GPL(int340x_thermal_update_trips); 247 248 MODULE_AUTHOR("Aaron Lu <aaron.lu@intel.com>"); 249 MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 250 MODULE_DESCRIPTION("Intel INT340x common thermal zone handler"); 251 MODULE_LICENSE("GPL v2"); 252