1/* 2 * INT3402 thermal driver for memory temperature reporting 3 * 4 * Copyright (C) 2014, Intel Corporation 5 * Authors: Aaron Lu <aaron.lu@intel.com> 6 * 7 * This program is free software; you can redistribute it and/or modify 8 * it under the terms of the GNU General Public License version 2 as 9 * published by the Free Software Foundation. 10 * 11 */ 12 13#include <linux/module.h> 14#include <linux/platform_device.h> 15#include <linux/acpi.h> 16#include <linux/thermal.h> 17 18#define ACPI_ACTIVE_COOLING_MAX_NR 10 19 20struct active_trip { 21 unsigned long temp; 22 int id; 23 bool valid; 24}; 25 26struct int3402_thermal_data { 27 unsigned long *aux_trips; 28 int aux_trip_nr; 29 unsigned long psv_temp; 30 int psv_trip_id; 31 unsigned long crt_temp; 32 int crt_trip_id; 33 unsigned long hot_temp; 34 int hot_trip_id; 35 struct active_trip act_trips[ACPI_ACTIVE_COOLING_MAX_NR]; 36 acpi_handle *handle; 37}; 38 39static int int3402_thermal_get_zone_temp(struct thermal_zone_device *zone, 40 unsigned long *temp) 41{ 42 struct int3402_thermal_data *d = zone->devdata; 43 unsigned long long tmp; 44 acpi_status status; 45 46 status = acpi_evaluate_integer(d->handle, "_TMP", NULL, &tmp); 47 if (ACPI_FAILURE(status)) 48 return -ENODEV; 49 50 /* _TMP returns the temperature in tenths of degrees Kelvin */ 51 *temp = DECI_KELVIN_TO_MILLICELSIUS(tmp); 52 53 return 0; 54} 55 56static int int3402_thermal_get_trip_temp(struct thermal_zone_device *zone, 57 int trip, unsigned long *temp) 58{ 59 struct int3402_thermal_data *d = zone->devdata; 60 int i; 61 62 if (trip < d->aux_trip_nr) 63 *temp = d->aux_trips[trip]; 64 else if (trip == d->crt_trip_id) 65 *temp = d->crt_temp; 66 else if (trip == d->psv_trip_id) 67 *temp = d->psv_temp; 68 else if (trip == d->hot_trip_id) 69 *temp = d->hot_temp; 70 else { 71 for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) { 72 if (d->act_trips[i].valid && 73 d->act_trips[i].id == trip) { 74 *temp = d->act_trips[i].temp; 75 break; 76 } 77 } 78 if (i == ACPI_ACTIVE_COOLING_MAX_NR) 79 return -EINVAL; 80 } 81 return 0; 82} 83 84static int int3402_thermal_get_trip_type(struct thermal_zone_device *zone, 85 int trip, enum thermal_trip_type *type) 86{ 87 struct int3402_thermal_data *d = zone->devdata; 88 int i; 89 90 if (trip < d->aux_trip_nr) 91 *type = THERMAL_TRIP_PASSIVE; 92 else if (trip == d->crt_trip_id) 93 *type = THERMAL_TRIP_CRITICAL; 94 else if (trip == d->hot_trip_id) 95 *type = THERMAL_TRIP_HOT; 96 else if (trip == d->psv_trip_id) 97 *type = THERMAL_TRIP_PASSIVE; 98 else { 99 for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) { 100 if (d->act_trips[i].valid && 101 d->act_trips[i].id == trip) { 102 *type = THERMAL_TRIP_ACTIVE; 103 break; 104 } 105 } 106 if (i == ACPI_ACTIVE_COOLING_MAX_NR) 107 return -EINVAL; 108 } 109 return 0; 110} 111 112static int int3402_thermal_set_trip_temp(struct thermal_zone_device *zone, int trip, 113 unsigned long temp) 114{ 115 struct int3402_thermal_data *d = zone->devdata; 116 acpi_status status; 117 char name[10]; 118 119 snprintf(name, sizeof(name), "PAT%d", trip); 120 status = acpi_execute_simple_method(d->handle, name, 121 MILLICELSIUS_TO_DECI_KELVIN(temp)); 122 if (ACPI_FAILURE(status)) 123 return -EIO; 124 125 d->aux_trips[trip] = temp; 126 return 0; 127} 128 129static struct thermal_zone_device_ops int3402_thermal_zone_ops = { 130 .get_temp = int3402_thermal_get_zone_temp, 131 .get_trip_temp = int3402_thermal_get_trip_temp, 132 .get_trip_type = int3402_thermal_get_trip_type, 133 .set_trip_temp = int3402_thermal_set_trip_temp, 134}; 135 136static struct thermal_zone_params int3402_thermal_params = { 137 .governor_name = "user_space", 138 .no_hwmon = true, 139}; 140 141static int int3402_thermal_get_temp(acpi_handle handle, char *name, 142 unsigned long *temp) 143{ 144 unsigned long long r; 145 acpi_status status; 146 147 status = acpi_evaluate_integer(handle, name, NULL, &r); 148 if (ACPI_FAILURE(status)) 149 return -EIO; 150 151 *temp = DECI_KELVIN_TO_MILLICELSIUS(r); 152 return 0; 153} 154 155static int int3402_thermal_probe(struct platform_device *pdev) 156{ 157 struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 158 struct int3402_thermal_data *d; 159 struct thermal_zone_device *zone; 160 acpi_status status; 161 unsigned long long trip_cnt; 162 int trip_mask = 0, i; 163 164 if (!acpi_has_method(adev->handle, "_TMP")) 165 return -ENODEV; 166 167 d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL); 168 if (!d) 169 return -ENOMEM; 170 171 status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt); 172 if (ACPI_FAILURE(status)) 173 trip_cnt = 0; 174 else { 175 d->aux_trips = devm_kzalloc(&pdev->dev, 176 sizeof(*d->aux_trips) * trip_cnt, GFP_KERNEL); 177 if (!d->aux_trips) 178 return -ENOMEM; 179 trip_mask = trip_cnt - 1; 180 d->handle = adev->handle; 181 d->aux_trip_nr = trip_cnt; 182 } 183 184 d->crt_trip_id = -1; 185 if (!int3402_thermal_get_temp(adev->handle, "_CRT", &d->crt_temp)) 186 d->crt_trip_id = trip_cnt++; 187 d->hot_trip_id = -1; 188 if (!int3402_thermal_get_temp(adev->handle, "_HOT", &d->hot_temp)) 189 d->hot_trip_id = trip_cnt++; 190 d->psv_trip_id = -1; 191 if (!int3402_thermal_get_temp(adev->handle, "_PSV", &d->psv_temp)) 192 d->psv_trip_id = trip_cnt++; 193 for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) { 194 char name[5] = { '_', 'A', 'C', '0' + i, '\0' }; 195 if (int3402_thermal_get_temp(adev->handle, name, 196 &d->act_trips[i].temp)) 197 break; 198 d->act_trips[i].id = trip_cnt++; 199 d->act_trips[i].valid = true; 200 } 201 202 zone = thermal_zone_device_register(acpi_device_bid(adev), trip_cnt, 203 trip_mask, d, 204 &int3402_thermal_zone_ops, 205 &int3402_thermal_params, 206 0, 0); 207 if (IS_ERR(zone)) 208 return PTR_ERR(zone); 209 platform_set_drvdata(pdev, zone); 210 211 return 0; 212} 213 214static int int3402_thermal_remove(struct platform_device *pdev) 215{ 216 struct thermal_zone_device *zone = platform_get_drvdata(pdev); 217 218 thermal_zone_device_unregister(zone); 219 return 0; 220} 221 222static const struct acpi_device_id int3402_thermal_match[] = { 223 {"INT3402", 0}, 224 {} 225}; 226 227MODULE_DEVICE_TABLE(acpi, int3402_thermal_match); 228 229static struct platform_driver int3402_thermal_driver = { 230 .probe = int3402_thermal_probe, 231 .remove = int3402_thermal_remove, 232 .driver = { 233 .name = "int3402 thermal", 234 .owner = THIS_MODULE, 235 .acpi_match_table = int3402_thermal_match, 236 }, 237}; 238 239module_platform_driver(int3402_thermal_driver); 240 241MODULE_DESCRIPTION("INT3402 Thermal driver"); 242MODULE_LICENSE("GPL"); 243