18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * ACPI INT3403 thermal driver 48c2ecf20Sopenharmony_ci * Copyright (c) 2013, Intel Corporation. 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/kernel.h> 88c2ecf20Sopenharmony_ci#include <linux/module.h> 98c2ecf20Sopenharmony_ci#include <linux/init.h> 108c2ecf20Sopenharmony_ci#include <linux/types.h> 118c2ecf20Sopenharmony_ci#include <linux/acpi.h> 128c2ecf20Sopenharmony_ci#include <linux/thermal.h> 138c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 148c2ecf20Sopenharmony_ci#include "int340x_thermal_zone.h" 158c2ecf20Sopenharmony_ci 168c2ecf20Sopenharmony_ci#define INT3403_TYPE_SENSOR 0x03 178c2ecf20Sopenharmony_ci#define INT3403_TYPE_CHARGER 0x0B 188c2ecf20Sopenharmony_ci#define INT3403_TYPE_BATTERY 0x0C 198c2ecf20Sopenharmony_ci#define INT3403_PERF_CHANGED_EVENT 0x80 208c2ecf20Sopenharmony_ci#define INT3403_PERF_TRIP_POINT_CHANGED 0x81 218c2ecf20Sopenharmony_ci#define INT3403_THERMAL_EVENT 0x90 228c2ecf20Sopenharmony_ci 238c2ecf20Sopenharmony_ci/* Preserved structure for future expandbility */ 248c2ecf20Sopenharmony_cistruct int3403_sensor { 258c2ecf20Sopenharmony_ci struct int34x_thermal_zone *int340x_zone; 268c2ecf20Sopenharmony_ci}; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistruct int3403_performance_state { 298c2ecf20Sopenharmony_ci u64 performance; 308c2ecf20Sopenharmony_ci u64 power; 318c2ecf20Sopenharmony_ci u64 latency; 328c2ecf20Sopenharmony_ci u64 linear; 338c2ecf20Sopenharmony_ci u64 control; 348c2ecf20Sopenharmony_ci u64 raw_performace; 358c2ecf20Sopenharmony_ci char *raw_unit; 368c2ecf20Sopenharmony_ci int reserved; 378c2ecf20Sopenharmony_ci}; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistruct int3403_cdev { 408c2ecf20Sopenharmony_ci struct thermal_cooling_device *cdev; 418c2ecf20Sopenharmony_ci unsigned long max_state; 428c2ecf20Sopenharmony_ci}; 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistruct int3403_priv { 458c2ecf20Sopenharmony_ci struct platform_device *pdev; 468c2ecf20Sopenharmony_ci struct acpi_device *adev; 478c2ecf20Sopenharmony_ci unsigned long long type; 488c2ecf20Sopenharmony_ci void *priv; 498c2ecf20Sopenharmony_ci}; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_cistatic void int3403_notify(acpi_handle handle, 528c2ecf20Sopenharmony_ci u32 event, void *data) 538c2ecf20Sopenharmony_ci{ 548c2ecf20Sopenharmony_ci struct int3403_priv *priv = data; 558c2ecf20Sopenharmony_ci struct int3403_sensor *obj; 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci if (!priv) 588c2ecf20Sopenharmony_ci return; 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_ci obj = priv->priv; 618c2ecf20Sopenharmony_ci if (priv->type != INT3403_TYPE_SENSOR || !obj) 628c2ecf20Sopenharmony_ci return; 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci switch (event) { 658c2ecf20Sopenharmony_ci case INT3403_PERF_CHANGED_EVENT: 668c2ecf20Sopenharmony_ci break; 678c2ecf20Sopenharmony_ci case INT3403_THERMAL_EVENT: 688c2ecf20Sopenharmony_ci int340x_thermal_zone_device_update(obj->int340x_zone, 698c2ecf20Sopenharmony_ci THERMAL_TRIP_VIOLATED); 708c2ecf20Sopenharmony_ci break; 718c2ecf20Sopenharmony_ci case INT3403_PERF_TRIP_POINT_CHANGED: 728c2ecf20Sopenharmony_ci int340x_thermal_read_trips(obj->int340x_zone); 738c2ecf20Sopenharmony_ci int340x_thermal_zone_device_update(obj->int340x_zone, 748c2ecf20Sopenharmony_ci THERMAL_TRIP_CHANGED); 758c2ecf20Sopenharmony_ci break; 768c2ecf20Sopenharmony_ci default: 778c2ecf20Sopenharmony_ci dev_dbg(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); 788c2ecf20Sopenharmony_ci break; 798c2ecf20Sopenharmony_ci } 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic int int3403_sensor_add(struct int3403_priv *priv) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci int result = 0; 858c2ecf20Sopenharmony_ci struct int3403_sensor *obj; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 888c2ecf20Sopenharmony_ci if (!obj) 898c2ecf20Sopenharmony_ci return -ENOMEM; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci priv->priv = obj; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL); 948c2ecf20Sopenharmony_ci if (IS_ERR(obj->int340x_zone)) 958c2ecf20Sopenharmony_ci return PTR_ERR(obj->int340x_zone); 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci result = acpi_install_notify_handler(priv->adev->handle, 988c2ecf20Sopenharmony_ci ACPI_DEVICE_NOTIFY, int3403_notify, 998c2ecf20Sopenharmony_ci (void *)priv); 1008c2ecf20Sopenharmony_ci if (result) 1018c2ecf20Sopenharmony_ci goto err_free_obj; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci return 0; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci err_free_obj: 1068c2ecf20Sopenharmony_ci int340x_thermal_zone_remove(obj->int340x_zone); 1078c2ecf20Sopenharmony_ci return result; 1088c2ecf20Sopenharmony_ci} 1098c2ecf20Sopenharmony_ci 1108c2ecf20Sopenharmony_cistatic int int3403_sensor_remove(struct int3403_priv *priv) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci struct int3403_sensor *obj = priv->priv; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci acpi_remove_notify_handler(priv->adev->handle, 1158c2ecf20Sopenharmony_ci ACPI_DEVICE_NOTIFY, int3403_notify); 1168c2ecf20Sopenharmony_ci int340x_thermal_zone_remove(obj->int340x_zone); 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci return 0; 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci/* INT3403 Cooling devices */ 1228c2ecf20Sopenharmony_cistatic int int3403_get_max_state(struct thermal_cooling_device *cdev, 1238c2ecf20Sopenharmony_ci unsigned long *state) 1248c2ecf20Sopenharmony_ci{ 1258c2ecf20Sopenharmony_ci struct int3403_priv *priv = cdev->devdata; 1268c2ecf20Sopenharmony_ci struct int3403_cdev *obj = priv->priv; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci *state = obj->max_state; 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_cistatic int int3403_get_cur_state(struct thermal_cooling_device *cdev, 1338c2ecf20Sopenharmony_ci unsigned long *state) 1348c2ecf20Sopenharmony_ci{ 1358c2ecf20Sopenharmony_ci struct int3403_priv *priv = cdev->devdata; 1368c2ecf20Sopenharmony_ci unsigned long long level; 1378c2ecf20Sopenharmony_ci acpi_status status; 1388c2ecf20Sopenharmony_ci 1398c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); 1408c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(status)) { 1418c2ecf20Sopenharmony_ci *state = level; 1428c2ecf20Sopenharmony_ci return 0; 1438c2ecf20Sopenharmony_ci } else 1448c2ecf20Sopenharmony_ci return -EINVAL; 1458c2ecf20Sopenharmony_ci} 1468c2ecf20Sopenharmony_ci 1478c2ecf20Sopenharmony_cistatic int 1488c2ecf20Sopenharmony_ciint3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) 1498c2ecf20Sopenharmony_ci{ 1508c2ecf20Sopenharmony_ci struct int3403_priv *priv = cdev->devdata; 1518c2ecf20Sopenharmony_ci acpi_status status; 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); 1548c2ecf20Sopenharmony_ci if (ACPI_SUCCESS(status)) 1558c2ecf20Sopenharmony_ci return 0; 1568c2ecf20Sopenharmony_ci else 1578c2ecf20Sopenharmony_ci return -EINVAL; 1588c2ecf20Sopenharmony_ci} 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_cistatic const struct thermal_cooling_device_ops int3403_cooling_ops = { 1618c2ecf20Sopenharmony_ci .get_max_state = int3403_get_max_state, 1628c2ecf20Sopenharmony_ci .get_cur_state = int3403_get_cur_state, 1638c2ecf20Sopenharmony_ci .set_cur_state = int3403_set_cur_state, 1648c2ecf20Sopenharmony_ci}; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_cistatic int int3403_cdev_add(struct int3403_priv *priv) 1678c2ecf20Sopenharmony_ci{ 1688c2ecf20Sopenharmony_ci int result = 0; 1698c2ecf20Sopenharmony_ci acpi_status status; 1708c2ecf20Sopenharmony_ci struct int3403_cdev *obj; 1718c2ecf20Sopenharmony_ci struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; 1728c2ecf20Sopenharmony_ci union acpi_object *p; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 1758c2ecf20Sopenharmony_ci if (!obj) 1768c2ecf20Sopenharmony_ci return -ENOMEM; 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); 1798c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) 1808c2ecf20Sopenharmony_ci return -ENODEV; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci p = buf.pointer; 1838c2ecf20Sopenharmony_ci if (!p || (p->type != ACPI_TYPE_PACKAGE)) { 1848c2ecf20Sopenharmony_ci pr_warn("Invalid PPSS data\n"); 1858c2ecf20Sopenharmony_ci kfree(buf.pointer); 1868c2ecf20Sopenharmony_ci return -EFAULT; 1878c2ecf20Sopenharmony_ci } 1888c2ecf20Sopenharmony_ci 1898c2ecf20Sopenharmony_ci priv->priv = obj; 1908c2ecf20Sopenharmony_ci obj->max_state = p->package.count - 1; 1918c2ecf20Sopenharmony_ci obj->cdev = 1928c2ecf20Sopenharmony_ci thermal_cooling_device_register(acpi_device_bid(priv->adev), 1938c2ecf20Sopenharmony_ci priv, &int3403_cooling_ops); 1948c2ecf20Sopenharmony_ci if (IS_ERR(obj->cdev)) 1958c2ecf20Sopenharmony_ci result = PTR_ERR(obj->cdev); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci kfree(buf.pointer); 1988c2ecf20Sopenharmony_ci /* TODO: add ACPI notification support */ 1998c2ecf20Sopenharmony_ci 2008c2ecf20Sopenharmony_ci return result; 2018c2ecf20Sopenharmony_ci} 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_cistatic int int3403_cdev_remove(struct int3403_priv *priv) 2048c2ecf20Sopenharmony_ci{ 2058c2ecf20Sopenharmony_ci struct int3403_cdev *obj = priv->priv; 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci thermal_cooling_device_unregister(obj->cdev); 2088c2ecf20Sopenharmony_ci return 0; 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic int int3403_add(struct platform_device *pdev) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci struct int3403_priv *priv; 2148c2ecf20Sopenharmony_ci int result = 0; 2158c2ecf20Sopenharmony_ci unsigned long long tmp; 2168c2ecf20Sopenharmony_ci acpi_status status; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), 2198c2ecf20Sopenharmony_ci GFP_KERNEL); 2208c2ecf20Sopenharmony_ci if (!priv) 2218c2ecf20Sopenharmony_ci return -ENOMEM; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci priv->pdev = pdev; 2248c2ecf20Sopenharmony_ci priv->adev = ACPI_COMPANION(&(pdev->dev)); 2258c2ecf20Sopenharmony_ci if (!priv->adev) { 2268c2ecf20Sopenharmony_ci result = -EINVAL; 2278c2ecf20Sopenharmony_ci goto err; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(priv->adev->handle, "_TMP", 2328c2ecf20Sopenharmony_ci NULL, &tmp); 2338c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 2348c2ecf20Sopenharmony_ci status = acpi_evaluate_integer(priv->adev->handle, "PTYP", 2358c2ecf20Sopenharmony_ci NULL, &priv->type); 2368c2ecf20Sopenharmony_ci if (ACPI_FAILURE(status)) { 2378c2ecf20Sopenharmony_ci result = -EINVAL; 2388c2ecf20Sopenharmony_ci goto err; 2398c2ecf20Sopenharmony_ci } 2408c2ecf20Sopenharmony_ci } else { 2418c2ecf20Sopenharmony_ci priv->type = INT3403_TYPE_SENSOR; 2428c2ecf20Sopenharmony_ci } 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, priv); 2458c2ecf20Sopenharmony_ci switch (priv->type) { 2468c2ecf20Sopenharmony_ci case INT3403_TYPE_SENSOR: 2478c2ecf20Sopenharmony_ci result = int3403_sensor_add(priv); 2488c2ecf20Sopenharmony_ci break; 2498c2ecf20Sopenharmony_ci case INT3403_TYPE_CHARGER: 2508c2ecf20Sopenharmony_ci case INT3403_TYPE_BATTERY: 2518c2ecf20Sopenharmony_ci result = int3403_cdev_add(priv); 2528c2ecf20Sopenharmony_ci break; 2538c2ecf20Sopenharmony_ci default: 2548c2ecf20Sopenharmony_ci result = -EINVAL; 2558c2ecf20Sopenharmony_ci } 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci if (result) 2588c2ecf20Sopenharmony_ci goto err; 2598c2ecf20Sopenharmony_ci return result; 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_cierr: 2628c2ecf20Sopenharmony_ci return result; 2638c2ecf20Sopenharmony_ci} 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_cistatic int int3403_remove(struct platform_device *pdev) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci struct int3403_priv *priv = platform_get_drvdata(pdev); 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci switch (priv->type) { 2708c2ecf20Sopenharmony_ci case INT3403_TYPE_SENSOR: 2718c2ecf20Sopenharmony_ci int3403_sensor_remove(priv); 2728c2ecf20Sopenharmony_ci break; 2738c2ecf20Sopenharmony_ci case INT3403_TYPE_CHARGER: 2748c2ecf20Sopenharmony_ci case INT3403_TYPE_BATTERY: 2758c2ecf20Sopenharmony_ci int3403_cdev_remove(priv); 2768c2ecf20Sopenharmony_ci break; 2778c2ecf20Sopenharmony_ci default: 2788c2ecf20Sopenharmony_ci break; 2798c2ecf20Sopenharmony_ci } 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci return 0; 2828c2ecf20Sopenharmony_ci} 2838c2ecf20Sopenharmony_ci 2848c2ecf20Sopenharmony_cistatic const struct acpi_device_id int3403_device_ids[] = { 2858c2ecf20Sopenharmony_ci {"INT3403", 0}, 2868c2ecf20Sopenharmony_ci {"INTC1043", 0}, 2878c2ecf20Sopenharmony_ci {"", 0}, 2888c2ecf20Sopenharmony_ci}; 2898c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, int3403_device_ids); 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_cistatic struct platform_driver int3403_driver = { 2928c2ecf20Sopenharmony_ci .probe = int3403_add, 2938c2ecf20Sopenharmony_ci .remove = int3403_remove, 2948c2ecf20Sopenharmony_ci .driver = { 2958c2ecf20Sopenharmony_ci .name = "int3403 thermal", 2968c2ecf20Sopenharmony_ci .acpi_match_table = int3403_device_ids, 2978c2ecf20Sopenharmony_ci }, 2988c2ecf20Sopenharmony_ci}; 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_cimodule_platform_driver(int3403_driver); 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ciMODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 3038c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3048c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("ACPI INT3403 thermal driver"); 305