13e8c4d31SAmit Kucheria /* 23e8c4d31SAmit Kucheria * ACPI INT3403 thermal driver 33e8c4d31SAmit Kucheria * Copyright (c) 2013, Intel Corporation. 43e8c4d31SAmit Kucheria * 53e8c4d31SAmit Kucheria * This program is free software; you can redistribute it and/or modify it 63e8c4d31SAmit Kucheria * under the terms and conditions of the GNU General Public License, 73e8c4d31SAmit Kucheria * version 2, as published by the Free Software Foundation. 83e8c4d31SAmit Kucheria * 93e8c4d31SAmit Kucheria * This program is distributed in the hope it will be useful, but WITHOUT 103e8c4d31SAmit Kucheria * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 113e8c4d31SAmit Kucheria * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 123e8c4d31SAmit Kucheria * more details. 133e8c4d31SAmit Kucheria */ 143e8c4d31SAmit Kucheria 153e8c4d31SAmit Kucheria #include <linux/kernel.h> 163e8c4d31SAmit Kucheria #include <linux/module.h> 173e8c4d31SAmit Kucheria #include <linux/init.h> 183e8c4d31SAmit Kucheria #include <linux/types.h> 193e8c4d31SAmit Kucheria #include <linux/acpi.h> 203e8c4d31SAmit Kucheria #include <linux/thermal.h> 213e8c4d31SAmit Kucheria #include <linux/platform_device.h> 223e8c4d31SAmit Kucheria #include "int340x_thermal_zone.h" 233e8c4d31SAmit Kucheria 243e8c4d31SAmit Kucheria #define INT3403_TYPE_SENSOR 0x03 253e8c4d31SAmit Kucheria #define INT3403_TYPE_CHARGER 0x0B 263e8c4d31SAmit Kucheria #define INT3403_TYPE_BATTERY 0x0C 273e8c4d31SAmit Kucheria #define INT3403_PERF_CHANGED_EVENT 0x80 283e8c4d31SAmit Kucheria #define INT3403_PERF_TRIP_POINT_CHANGED 0x81 293e8c4d31SAmit Kucheria #define INT3403_THERMAL_EVENT 0x90 303e8c4d31SAmit Kucheria 313e8c4d31SAmit Kucheria /* Preserved structure for future expandbility */ 323e8c4d31SAmit Kucheria struct int3403_sensor { 333e8c4d31SAmit Kucheria struct int34x_thermal_zone *int340x_zone; 343e8c4d31SAmit Kucheria }; 353e8c4d31SAmit Kucheria 363e8c4d31SAmit Kucheria struct int3403_performance_state { 373e8c4d31SAmit Kucheria u64 performance; 383e8c4d31SAmit Kucheria u64 power; 393e8c4d31SAmit Kucheria u64 latency; 403e8c4d31SAmit Kucheria u64 linear; 413e8c4d31SAmit Kucheria u64 control; 423e8c4d31SAmit Kucheria u64 raw_performace; 433e8c4d31SAmit Kucheria char *raw_unit; 443e8c4d31SAmit Kucheria int reserved; 453e8c4d31SAmit Kucheria }; 463e8c4d31SAmit Kucheria 473e8c4d31SAmit Kucheria struct int3403_cdev { 483e8c4d31SAmit Kucheria struct thermal_cooling_device *cdev; 493e8c4d31SAmit Kucheria unsigned long max_state; 503e8c4d31SAmit Kucheria }; 513e8c4d31SAmit Kucheria 523e8c4d31SAmit Kucheria struct int3403_priv { 533e8c4d31SAmit Kucheria struct platform_device *pdev; 543e8c4d31SAmit Kucheria struct acpi_device *adev; 553e8c4d31SAmit Kucheria unsigned long long type; 563e8c4d31SAmit Kucheria void *priv; 573e8c4d31SAmit Kucheria }; 583e8c4d31SAmit Kucheria 593e8c4d31SAmit Kucheria static void int3403_notify(acpi_handle handle, 603e8c4d31SAmit Kucheria u32 event, void *data) 613e8c4d31SAmit Kucheria { 623e8c4d31SAmit Kucheria struct int3403_priv *priv = data; 633e8c4d31SAmit Kucheria struct int3403_sensor *obj; 643e8c4d31SAmit Kucheria 653e8c4d31SAmit Kucheria if (!priv) 663e8c4d31SAmit Kucheria return; 673e8c4d31SAmit Kucheria 683e8c4d31SAmit Kucheria obj = priv->priv; 693e8c4d31SAmit Kucheria if (priv->type != INT3403_TYPE_SENSOR || !obj) 703e8c4d31SAmit Kucheria return; 713e8c4d31SAmit Kucheria 723e8c4d31SAmit Kucheria switch (event) { 733e8c4d31SAmit Kucheria case INT3403_PERF_CHANGED_EVENT: 743e8c4d31SAmit Kucheria break; 753e8c4d31SAmit Kucheria case INT3403_THERMAL_EVENT: 763e8c4d31SAmit Kucheria int340x_thermal_zone_device_update(obj->int340x_zone, 773e8c4d31SAmit Kucheria THERMAL_TRIP_VIOLATED); 783e8c4d31SAmit Kucheria break; 793e8c4d31SAmit Kucheria case INT3403_PERF_TRIP_POINT_CHANGED: 803e8c4d31SAmit Kucheria int340x_thermal_read_trips(obj->int340x_zone); 813e8c4d31SAmit Kucheria int340x_thermal_zone_device_update(obj->int340x_zone, 823e8c4d31SAmit Kucheria THERMAL_TRIP_CHANGED); 833e8c4d31SAmit Kucheria break; 843e8c4d31SAmit Kucheria default: 853e8c4d31SAmit Kucheria dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); 863e8c4d31SAmit Kucheria break; 873e8c4d31SAmit Kucheria } 883e8c4d31SAmit Kucheria } 893e8c4d31SAmit Kucheria 903e8c4d31SAmit Kucheria static int int3403_sensor_add(struct int3403_priv *priv) 913e8c4d31SAmit Kucheria { 923e8c4d31SAmit Kucheria int result = 0; 933e8c4d31SAmit Kucheria struct int3403_sensor *obj; 943e8c4d31SAmit Kucheria 953e8c4d31SAmit Kucheria obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 963e8c4d31SAmit Kucheria if (!obj) 973e8c4d31SAmit Kucheria return -ENOMEM; 983e8c4d31SAmit Kucheria 993e8c4d31SAmit Kucheria priv->priv = obj; 1003e8c4d31SAmit Kucheria 1013e8c4d31SAmit Kucheria obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL); 1023e8c4d31SAmit Kucheria if (IS_ERR(obj->int340x_zone)) 1033e8c4d31SAmit Kucheria return PTR_ERR(obj->int340x_zone); 1043e8c4d31SAmit Kucheria 1053e8c4d31SAmit Kucheria result = acpi_install_notify_handler(priv->adev->handle, 1063e8c4d31SAmit Kucheria ACPI_DEVICE_NOTIFY, int3403_notify, 1073e8c4d31SAmit Kucheria (void *)priv); 1083e8c4d31SAmit Kucheria if (result) 1093e8c4d31SAmit Kucheria goto err_free_obj; 1103e8c4d31SAmit Kucheria 1113e8c4d31SAmit Kucheria return 0; 1123e8c4d31SAmit Kucheria 1133e8c4d31SAmit Kucheria err_free_obj: 1143e8c4d31SAmit Kucheria int340x_thermal_zone_remove(obj->int340x_zone); 1153e8c4d31SAmit Kucheria return result; 1163e8c4d31SAmit Kucheria } 1173e8c4d31SAmit Kucheria 1183e8c4d31SAmit Kucheria static int int3403_sensor_remove(struct int3403_priv *priv) 1193e8c4d31SAmit Kucheria { 1203e8c4d31SAmit Kucheria struct int3403_sensor *obj = priv->priv; 1213e8c4d31SAmit Kucheria 1223e8c4d31SAmit Kucheria acpi_remove_notify_handler(priv->adev->handle, 1233e8c4d31SAmit Kucheria ACPI_DEVICE_NOTIFY, int3403_notify); 1243e8c4d31SAmit Kucheria int340x_thermal_zone_remove(obj->int340x_zone); 1253e8c4d31SAmit Kucheria 1263e8c4d31SAmit Kucheria return 0; 1273e8c4d31SAmit Kucheria } 1283e8c4d31SAmit Kucheria 1293e8c4d31SAmit Kucheria /* INT3403 Cooling devices */ 1303e8c4d31SAmit Kucheria static int int3403_get_max_state(struct thermal_cooling_device *cdev, 1313e8c4d31SAmit Kucheria unsigned long *state) 1323e8c4d31SAmit Kucheria { 1333e8c4d31SAmit Kucheria struct int3403_priv *priv = cdev->devdata; 1343e8c4d31SAmit Kucheria struct int3403_cdev *obj = priv->priv; 1353e8c4d31SAmit Kucheria 1363e8c4d31SAmit Kucheria *state = obj->max_state; 1373e8c4d31SAmit Kucheria return 0; 1383e8c4d31SAmit Kucheria } 1393e8c4d31SAmit Kucheria 1403e8c4d31SAmit Kucheria static int int3403_get_cur_state(struct thermal_cooling_device *cdev, 1413e8c4d31SAmit Kucheria unsigned long *state) 1423e8c4d31SAmit Kucheria { 1433e8c4d31SAmit Kucheria struct int3403_priv *priv = cdev->devdata; 1443e8c4d31SAmit Kucheria unsigned long long level; 1453e8c4d31SAmit Kucheria acpi_status status; 1463e8c4d31SAmit Kucheria 1473e8c4d31SAmit Kucheria status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); 1483e8c4d31SAmit Kucheria if (ACPI_SUCCESS(status)) { 1493e8c4d31SAmit Kucheria *state = level; 1503e8c4d31SAmit Kucheria return 0; 1513e8c4d31SAmit Kucheria } else 1523e8c4d31SAmit Kucheria return -EINVAL; 1533e8c4d31SAmit Kucheria } 1543e8c4d31SAmit Kucheria 1553e8c4d31SAmit Kucheria static int 1563e8c4d31SAmit Kucheria int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) 1573e8c4d31SAmit Kucheria { 1583e8c4d31SAmit Kucheria struct int3403_priv *priv = cdev->devdata; 1593e8c4d31SAmit Kucheria acpi_status status; 1603e8c4d31SAmit Kucheria 1613e8c4d31SAmit Kucheria status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); 1623e8c4d31SAmit Kucheria if (ACPI_SUCCESS(status)) 1633e8c4d31SAmit Kucheria return 0; 1643e8c4d31SAmit Kucheria else 1653e8c4d31SAmit Kucheria return -EINVAL; 1663e8c4d31SAmit Kucheria } 1673e8c4d31SAmit Kucheria 1683e8c4d31SAmit Kucheria static const struct thermal_cooling_device_ops int3403_cooling_ops = { 1693e8c4d31SAmit Kucheria .get_max_state = int3403_get_max_state, 1703e8c4d31SAmit Kucheria .get_cur_state = int3403_get_cur_state, 1713e8c4d31SAmit Kucheria .set_cur_state = int3403_set_cur_state, 1723e8c4d31SAmit Kucheria }; 1733e8c4d31SAmit Kucheria 1743e8c4d31SAmit Kucheria static int int3403_cdev_add(struct int3403_priv *priv) 1753e8c4d31SAmit Kucheria { 1763e8c4d31SAmit Kucheria int result = 0; 1773e8c4d31SAmit Kucheria acpi_status status; 1783e8c4d31SAmit Kucheria struct int3403_cdev *obj; 1793e8c4d31SAmit Kucheria struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; 1803e8c4d31SAmit Kucheria union acpi_object *p; 1813e8c4d31SAmit Kucheria 1823e8c4d31SAmit Kucheria obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 1833e8c4d31SAmit Kucheria if (!obj) 1843e8c4d31SAmit Kucheria return -ENOMEM; 1853e8c4d31SAmit Kucheria 1863e8c4d31SAmit Kucheria status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); 1873e8c4d31SAmit Kucheria if (ACPI_FAILURE(status)) 1883e8c4d31SAmit Kucheria return -ENODEV; 1893e8c4d31SAmit Kucheria 1903e8c4d31SAmit Kucheria p = buf.pointer; 1913e8c4d31SAmit Kucheria if (!p || (p->type != ACPI_TYPE_PACKAGE)) { 1923e8c4d31SAmit Kucheria printk(KERN_WARNING "Invalid PPSS data\n"); 1933e8c4d31SAmit Kucheria kfree(buf.pointer); 1943e8c4d31SAmit Kucheria return -EFAULT; 1953e8c4d31SAmit Kucheria } 1963e8c4d31SAmit Kucheria 1973e8c4d31SAmit Kucheria priv->priv = obj; 1983e8c4d31SAmit Kucheria obj->max_state = p->package.count - 1; 1993e8c4d31SAmit Kucheria obj->cdev = 2003e8c4d31SAmit Kucheria thermal_cooling_device_register(acpi_device_bid(priv->adev), 2013e8c4d31SAmit Kucheria priv, &int3403_cooling_ops); 2023e8c4d31SAmit Kucheria if (IS_ERR(obj->cdev)) 2033e8c4d31SAmit Kucheria result = PTR_ERR(obj->cdev); 2043e8c4d31SAmit Kucheria 2053e8c4d31SAmit Kucheria kfree(buf.pointer); 2063e8c4d31SAmit Kucheria /* TODO: add ACPI notification support */ 2073e8c4d31SAmit Kucheria 2083e8c4d31SAmit Kucheria return result; 2093e8c4d31SAmit Kucheria } 2103e8c4d31SAmit Kucheria 2113e8c4d31SAmit Kucheria static int int3403_cdev_remove(struct int3403_priv *priv) 2123e8c4d31SAmit Kucheria { 2133e8c4d31SAmit Kucheria struct int3403_cdev *obj = priv->priv; 2143e8c4d31SAmit Kucheria 2153e8c4d31SAmit Kucheria thermal_cooling_device_unregister(obj->cdev); 2163e8c4d31SAmit Kucheria return 0; 2173e8c4d31SAmit Kucheria } 2183e8c4d31SAmit Kucheria 2193e8c4d31SAmit Kucheria static int int3403_add(struct platform_device *pdev) 2203e8c4d31SAmit Kucheria { 2213e8c4d31SAmit Kucheria struct int3403_priv *priv; 2223e8c4d31SAmit Kucheria int result = 0; 2233e8c4d31SAmit Kucheria acpi_status status; 2243e8c4d31SAmit Kucheria 2253e8c4d31SAmit Kucheria priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), 2263e8c4d31SAmit Kucheria GFP_KERNEL); 2273e8c4d31SAmit Kucheria if (!priv) 2283e8c4d31SAmit Kucheria return -ENOMEM; 2293e8c4d31SAmit Kucheria 2303e8c4d31SAmit Kucheria priv->pdev = pdev; 2313e8c4d31SAmit Kucheria priv->adev = ACPI_COMPANION(&(pdev->dev)); 2323e8c4d31SAmit Kucheria if (!priv->adev) { 2333e8c4d31SAmit Kucheria result = -EINVAL; 2343e8c4d31SAmit Kucheria goto err; 2353e8c4d31SAmit Kucheria } 2363e8c4d31SAmit Kucheria 2373e8c4d31SAmit Kucheria status = acpi_evaluate_integer(priv->adev->handle, "PTYP", 2383e8c4d31SAmit Kucheria NULL, &priv->type); 2393e8c4d31SAmit Kucheria if (ACPI_FAILURE(status)) { 2403e8c4d31SAmit Kucheria unsigned long long tmp; 2413e8c4d31SAmit Kucheria 2423e8c4d31SAmit Kucheria status = acpi_evaluate_integer(priv->adev->handle, "_TMP", 2433e8c4d31SAmit Kucheria NULL, &tmp); 2443e8c4d31SAmit Kucheria if (ACPI_FAILURE(status)) { 2453e8c4d31SAmit Kucheria result = -EINVAL; 2463e8c4d31SAmit Kucheria goto err; 2473e8c4d31SAmit Kucheria } else { 2483e8c4d31SAmit Kucheria priv->type = INT3403_TYPE_SENSOR; 2493e8c4d31SAmit Kucheria } 2503e8c4d31SAmit Kucheria } 2513e8c4d31SAmit Kucheria 2523e8c4d31SAmit Kucheria platform_set_drvdata(pdev, priv); 2533e8c4d31SAmit Kucheria switch (priv->type) { 2543e8c4d31SAmit Kucheria case INT3403_TYPE_SENSOR: 2553e8c4d31SAmit Kucheria result = int3403_sensor_add(priv); 2563e8c4d31SAmit Kucheria break; 2573e8c4d31SAmit Kucheria case INT3403_TYPE_CHARGER: 2583e8c4d31SAmit Kucheria case INT3403_TYPE_BATTERY: 2593e8c4d31SAmit Kucheria result = int3403_cdev_add(priv); 2603e8c4d31SAmit Kucheria break; 2613e8c4d31SAmit Kucheria default: 2623e8c4d31SAmit Kucheria result = -EINVAL; 2633e8c4d31SAmit Kucheria } 2643e8c4d31SAmit Kucheria 2653e8c4d31SAmit Kucheria if (result) 2663e8c4d31SAmit Kucheria goto err; 2673e8c4d31SAmit Kucheria return result; 2683e8c4d31SAmit Kucheria 2693e8c4d31SAmit Kucheria err: 2703e8c4d31SAmit Kucheria return result; 2713e8c4d31SAmit Kucheria } 2723e8c4d31SAmit Kucheria 2733e8c4d31SAmit Kucheria static int int3403_remove(struct platform_device *pdev) 2743e8c4d31SAmit Kucheria { 2753e8c4d31SAmit Kucheria struct int3403_priv *priv = platform_get_drvdata(pdev); 2763e8c4d31SAmit Kucheria 2773e8c4d31SAmit Kucheria switch (priv->type) { 2783e8c4d31SAmit Kucheria case INT3403_TYPE_SENSOR: 2793e8c4d31SAmit Kucheria int3403_sensor_remove(priv); 2803e8c4d31SAmit Kucheria break; 2813e8c4d31SAmit Kucheria case INT3403_TYPE_CHARGER: 2823e8c4d31SAmit Kucheria case INT3403_TYPE_BATTERY: 2833e8c4d31SAmit Kucheria int3403_cdev_remove(priv); 2843e8c4d31SAmit Kucheria break; 2853e8c4d31SAmit Kucheria default: 2863e8c4d31SAmit Kucheria break; 2873e8c4d31SAmit Kucheria } 2883e8c4d31SAmit Kucheria 2893e8c4d31SAmit Kucheria return 0; 2903e8c4d31SAmit Kucheria } 2913e8c4d31SAmit Kucheria 2923e8c4d31SAmit Kucheria static const struct acpi_device_id int3403_device_ids[] = { 2933e8c4d31SAmit Kucheria {"INT3403", 0}, 2943e8c4d31SAmit Kucheria {"", 0}, 2953e8c4d31SAmit Kucheria }; 2963e8c4d31SAmit Kucheria MODULE_DEVICE_TABLE(acpi, int3403_device_ids); 2973e8c4d31SAmit Kucheria 2983e8c4d31SAmit Kucheria static struct platform_driver int3403_driver = { 2993e8c4d31SAmit Kucheria .probe = int3403_add, 3003e8c4d31SAmit Kucheria .remove = int3403_remove, 3013e8c4d31SAmit Kucheria .driver = { 3023e8c4d31SAmit Kucheria .name = "int3403 thermal", 3033e8c4d31SAmit Kucheria .acpi_match_table = int3403_device_ids, 3043e8c4d31SAmit Kucheria }, 3053e8c4d31SAmit Kucheria }; 3063e8c4d31SAmit Kucheria 3073e8c4d31SAmit Kucheria module_platform_driver(int3403_driver); 3083e8c4d31SAmit Kucheria 3093e8c4d31SAmit Kucheria MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 3103e8c4d31SAmit Kucheria MODULE_LICENSE("GPL v2"); 3113e8c4d31SAmit Kucheria MODULE_DESCRIPTION("ACPI INT3403 thermal driver"); 312