1/* 2 * ACPI INT3403 thermal driver 3 * Copyright (c) 2013, 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/types.h> 19#include <linux/acpi.h> 20#include <linux/thermal.h> 21#include <linux/platform_device.h> 22 23#define INT3403_TYPE_SENSOR 0x03 24#define INT3403_TYPE_CHARGER 0x0B 25#define INT3403_TYPE_BATTERY 0x0C 26#define INT3403_PERF_CHANGED_EVENT 0x80 27#define INT3403_THERMAL_EVENT 0x90 28 29#define DECI_KELVIN_TO_MILLI_CELSIUS(t, off) (((t) - (off)) * 100) 30#define KELVIN_OFFSET 2732 31#define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off)) 32 33struct int3403_sensor { 34 struct thermal_zone_device *tzone; 35 unsigned long *thresholds; 36 unsigned long crit_temp; 37 int crit_trip_id; 38 unsigned long psv_temp; 39 int psv_trip_id; 40 41}; 42 43struct int3403_performance_state { 44 u64 performance; 45 u64 power; 46 u64 latency; 47 u64 linear; 48 u64 control; 49 u64 raw_performace; 50 char *raw_unit; 51 int reserved; 52}; 53 54struct int3403_cdev { 55 struct thermal_cooling_device *cdev; 56 unsigned long max_state; 57}; 58 59struct int3403_priv { 60 struct platform_device *pdev; 61 struct acpi_device *adev; 62 unsigned long long type; 63 void *priv; 64}; 65 66static int sys_get_curr_temp(struct thermal_zone_device *tzone, 67 unsigned long *temp) 68{ 69 struct int3403_priv *priv = tzone->devdata; 70 struct acpi_device *device = priv->adev; 71 unsigned long long tmp; 72 acpi_status status; 73 74 status = acpi_evaluate_integer(device->handle, "_TMP", NULL, &tmp); 75 if (ACPI_FAILURE(status)) 76 return -EIO; 77 78 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(tmp, KELVIN_OFFSET); 79 80 return 0; 81} 82 83static int sys_get_trip_hyst(struct thermal_zone_device *tzone, 84 int trip, unsigned long *temp) 85{ 86 struct int3403_priv *priv = tzone->devdata; 87 struct acpi_device *device = priv->adev; 88 unsigned long long hyst; 89 acpi_status status; 90 91 status = acpi_evaluate_integer(device->handle, "GTSH", NULL, &hyst); 92 if (ACPI_FAILURE(status)) 93 return -EIO; 94 95 /* 96 * Thermal hysteresis represents a temperature difference. 97 * Kelvin and Celsius have same degree size. So the 98 * conversion here between tenths of degree Kelvin unit 99 * and Milli-Celsius unit is just to multiply 100. 100 */ 101 *temp = hyst * 100; 102 103 return 0; 104} 105 106static int sys_get_trip_temp(struct thermal_zone_device *tzone, 107 int trip, unsigned long *temp) 108{ 109 struct int3403_priv *priv = tzone->devdata; 110 struct int3403_sensor *obj = priv->priv; 111 112 if (priv->type != INT3403_TYPE_SENSOR || !obj) 113 return -EINVAL; 114 115 if (trip == obj->crit_trip_id) 116 *temp = obj->crit_temp; 117 else if (trip == obj->psv_trip_id) 118 *temp = obj->psv_temp; 119 else { 120 /* 121 * get_trip_temp is a mandatory callback but 122 * PATx method doesn't return any value, so return 123 * cached value, which was last set from user space 124 */ 125 *temp = obj->thresholds[trip]; 126 } 127 128 return 0; 129} 130 131static int sys_get_trip_type(struct thermal_zone_device *thermal, 132 int trip, enum thermal_trip_type *type) 133{ 134 struct int3403_priv *priv = thermal->devdata; 135 struct int3403_sensor *obj = priv->priv; 136 137 /* Mandatory callback, may not mean much here */ 138 if (trip == obj->crit_trip_id) 139 *type = THERMAL_TRIP_CRITICAL; 140 else 141 *type = THERMAL_TRIP_PASSIVE; 142 143 return 0; 144} 145 146int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip, 147 unsigned long temp) 148{ 149 struct int3403_priv *priv = tzone->devdata; 150 struct acpi_device *device = priv->adev; 151 struct int3403_sensor *obj = priv->priv; 152 acpi_status status; 153 char name[10]; 154 int ret = 0; 155 156 snprintf(name, sizeof(name), "PAT%d", trip); 157 if (acpi_has_method(device->handle, name)) { 158 status = acpi_execute_simple_method(device->handle, name, 159 MILLI_CELSIUS_TO_DECI_KELVIN(temp, 160 KELVIN_OFFSET)); 161 if (ACPI_FAILURE(status)) 162 ret = -EIO; 163 else 164 obj->thresholds[trip] = temp; 165 } else { 166 ret = -EIO; 167 dev_err(&device->dev, "sys_set_trip_temp: method not found\n"); 168 } 169 170 return ret; 171} 172 173static struct thermal_zone_device_ops tzone_ops = { 174 .get_temp = sys_get_curr_temp, 175 .get_trip_temp = sys_get_trip_temp, 176 .get_trip_type = sys_get_trip_type, 177 .set_trip_temp = sys_set_trip_temp, 178 .get_trip_hyst = sys_get_trip_hyst, 179}; 180 181static struct thermal_zone_params int3403_thermal_params = { 182 .governor_name = "user_space", 183 .no_hwmon = true, 184}; 185 186static void int3403_notify(acpi_handle handle, 187 u32 event, void *data) 188{ 189 struct int3403_priv *priv = data; 190 struct int3403_sensor *obj; 191 192 if (!priv) 193 return; 194 195 obj = priv->priv; 196 if (priv->type != INT3403_TYPE_SENSOR || !obj) 197 return; 198 199 switch (event) { 200 case INT3403_PERF_CHANGED_EVENT: 201 break; 202 case INT3403_THERMAL_EVENT: 203 thermal_zone_device_update(obj->tzone); 204 break; 205 default: 206 dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); 207 break; 208 } 209} 210 211static int sys_get_trip_crt(struct acpi_device *device, unsigned long *temp) 212{ 213 unsigned long long crt; 214 acpi_status status; 215 216 status = acpi_evaluate_integer(device->handle, "_CRT", NULL, &crt); 217 if (ACPI_FAILURE(status)) 218 return -EIO; 219 220 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(crt, KELVIN_OFFSET); 221 222 return 0; 223} 224 225static int sys_get_trip_psv(struct acpi_device *device, unsigned long *temp) 226{ 227 unsigned long long psv; 228 acpi_status status; 229 230 status = acpi_evaluate_integer(device->handle, "_PSV", NULL, &psv); 231 if (ACPI_FAILURE(status)) 232 return -EIO; 233 234 *temp = DECI_KELVIN_TO_MILLI_CELSIUS(psv, KELVIN_OFFSET); 235 236 return 0; 237} 238 239static int int3403_sensor_add(struct int3403_priv *priv) 240{ 241 int result = 0; 242 acpi_status status; 243 struct int3403_sensor *obj; 244 unsigned long long trip_cnt; 245 int trip_mask = 0; 246 247 obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 248 if (!obj) 249 return -ENOMEM; 250 251 priv->priv = obj; 252 253 status = acpi_evaluate_integer(priv->adev->handle, "PATC", NULL, 254 &trip_cnt); 255 if (ACPI_FAILURE(status)) 256 trip_cnt = 0; 257 258 if (trip_cnt) { 259 /* We have to cache, thresholds can't be readback */ 260 obj->thresholds = devm_kzalloc(&priv->pdev->dev, 261 sizeof(*obj->thresholds) * trip_cnt, 262 GFP_KERNEL); 263 if (!obj->thresholds) { 264 result = -ENOMEM; 265 goto err_free_obj; 266 } 267 trip_mask = BIT(trip_cnt) - 1; 268 } 269 270 obj->psv_trip_id = -1; 271 if (!sys_get_trip_psv(priv->adev, &obj->psv_temp)) 272 obj->psv_trip_id = trip_cnt++; 273 274 obj->crit_trip_id = -1; 275 if (!sys_get_trip_crt(priv->adev, &obj->crit_temp)) 276 obj->crit_trip_id = trip_cnt++; 277 278 obj->tzone = thermal_zone_device_register(acpi_device_bid(priv->adev), 279 trip_cnt, trip_mask, priv, &tzone_ops, 280 &int3403_thermal_params, 0, 0); 281 if (IS_ERR(obj->tzone)) { 282 result = PTR_ERR(obj->tzone); 283 obj->tzone = NULL; 284 goto err_free_obj; 285 } 286 287 result = acpi_install_notify_handler(priv->adev->handle, 288 ACPI_DEVICE_NOTIFY, int3403_notify, 289 (void *)priv); 290 if (result) 291 goto err_free_obj; 292 293 return 0; 294 295 err_free_obj: 296 if (obj->tzone) 297 thermal_zone_device_unregister(obj->tzone); 298 return result; 299} 300 301static int int3403_sensor_remove(struct int3403_priv *priv) 302{ 303 struct int3403_sensor *obj = priv->priv; 304 305 thermal_zone_device_unregister(obj->tzone); 306 return 0; 307} 308 309/* INT3403 Cooling devices */ 310static int int3403_get_max_state(struct thermal_cooling_device *cdev, 311 unsigned long *state) 312{ 313 struct int3403_priv *priv = cdev->devdata; 314 struct int3403_cdev *obj = priv->priv; 315 316 *state = obj->max_state; 317 return 0; 318} 319 320static int int3403_get_cur_state(struct thermal_cooling_device *cdev, 321 unsigned long *state) 322{ 323 struct int3403_priv *priv = cdev->devdata; 324 unsigned long long level; 325 acpi_status status; 326 327 status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); 328 if (ACPI_SUCCESS(status)) { 329 *state = level; 330 return 0; 331 } else 332 return -EINVAL; 333} 334 335static int 336int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) 337{ 338 struct int3403_priv *priv = cdev->devdata; 339 acpi_status status; 340 341 status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); 342 if (ACPI_SUCCESS(status)) 343 return 0; 344 else 345 return -EINVAL; 346} 347 348static const struct thermal_cooling_device_ops int3403_cooling_ops = { 349 .get_max_state = int3403_get_max_state, 350 .get_cur_state = int3403_get_cur_state, 351 .set_cur_state = int3403_set_cur_state, 352}; 353 354static int int3403_cdev_add(struct int3403_priv *priv) 355{ 356 int result = 0; 357 acpi_status status; 358 struct int3403_cdev *obj; 359 struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; 360 union acpi_object *p; 361 362 obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 363 if (!obj) 364 return -ENOMEM; 365 366 status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); 367 if (ACPI_FAILURE(status)) 368 return -ENODEV; 369 370 p = buf.pointer; 371 if (!p || (p->type != ACPI_TYPE_PACKAGE)) { 372 printk(KERN_WARNING "Invalid PPSS data\n"); 373 return -EFAULT; 374 } 375 376 obj->max_state = p->package.count - 1; 377 obj->cdev = 378 thermal_cooling_device_register(acpi_device_bid(priv->adev), 379 priv, &int3403_cooling_ops); 380 if (IS_ERR(obj->cdev)) 381 result = PTR_ERR(obj->cdev); 382 383 priv->priv = obj; 384 385 /* TODO: add ACPI notification support */ 386 387 return result; 388} 389 390static int int3403_cdev_remove(struct int3403_priv *priv) 391{ 392 struct int3403_cdev *obj = priv->priv; 393 394 thermal_cooling_device_unregister(obj->cdev); 395 return 0; 396} 397 398static int int3403_add(struct platform_device *pdev) 399{ 400 struct int3403_priv *priv; 401 int result = 0; 402 acpi_status status; 403 404 priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), 405 GFP_KERNEL); 406 if (!priv) 407 return -ENOMEM; 408 409 priv->pdev = pdev; 410 priv->adev = ACPI_COMPANION(&(pdev->dev)); 411 if (!priv->adev) { 412 result = -EINVAL; 413 goto err; 414 } 415 416 status = acpi_evaluate_integer(priv->adev->handle, "PTYP", 417 NULL, &priv->type); 418 if (ACPI_FAILURE(status)) { 419 result = -EINVAL; 420 goto err; 421 } 422 423 platform_set_drvdata(pdev, priv); 424 switch (priv->type) { 425 case INT3403_TYPE_SENSOR: 426 result = int3403_sensor_add(priv); 427 break; 428 case INT3403_TYPE_CHARGER: 429 case INT3403_TYPE_BATTERY: 430 result = int3403_cdev_add(priv); 431 break; 432 default: 433 result = -EINVAL; 434 } 435 436 if (result) 437 goto err; 438 return result; 439 440err: 441 return result; 442} 443 444static int int3403_remove(struct platform_device *pdev) 445{ 446 struct int3403_priv *priv = platform_get_drvdata(pdev); 447 448 switch (priv->type) { 449 case INT3403_TYPE_SENSOR: 450 int3403_sensor_remove(priv); 451 break; 452 case INT3403_TYPE_CHARGER: 453 case INT3403_TYPE_BATTERY: 454 int3403_cdev_remove(priv); 455 break; 456 default: 457 break; 458 } 459 460 return 0; 461} 462 463static const struct acpi_device_id int3403_device_ids[] = { 464 {"INT3403", 0}, 465 {"", 0}, 466}; 467MODULE_DEVICE_TABLE(acpi, int3403_device_ids); 468 469static struct platform_driver int3403_driver = { 470 .probe = int3403_add, 471 .remove = int3403_remove, 472 .driver = { 473 .name = "int3403 thermal", 474 .owner = THIS_MODULE, 475 .acpi_match_table = int3403_device_ids, 476 }, 477}; 478 479module_platform_driver(int3403_driver); 480 481MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 482MODULE_LICENSE("GPL v2"); 483MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); 484