162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * ACPI INT3403 thermal driver 462306a36Sopenharmony_ci * Copyright (c) 2013, Intel Corporation. 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/kernel.h> 862306a36Sopenharmony_ci#include <linux/module.h> 962306a36Sopenharmony_ci#include <linux/init.h> 1062306a36Sopenharmony_ci#include <linux/types.h> 1162306a36Sopenharmony_ci#include <linux/acpi.h> 1262306a36Sopenharmony_ci#include <linux/thermal.h> 1362306a36Sopenharmony_ci#include <linux/platform_device.h> 1462306a36Sopenharmony_ci#include "int340x_thermal_zone.h" 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_ci#define INT3403_TYPE_SENSOR 0x03 1762306a36Sopenharmony_ci#define INT3403_TYPE_CHARGER 0x0B 1862306a36Sopenharmony_ci#define INT3403_TYPE_BATTERY 0x0C 1962306a36Sopenharmony_ci#define INT3403_PERF_CHANGED_EVENT 0x80 2062306a36Sopenharmony_ci#define INT3403_PERF_TRIP_POINT_CHANGED 0x81 2162306a36Sopenharmony_ci#define INT3403_THERMAL_EVENT 0x90 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci/* Preserved structure for future expandbility */ 2462306a36Sopenharmony_cistruct int3403_sensor { 2562306a36Sopenharmony_ci struct int34x_thermal_zone *int340x_zone; 2662306a36Sopenharmony_ci}; 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_cistruct int3403_performance_state { 2962306a36Sopenharmony_ci u64 performance; 3062306a36Sopenharmony_ci u64 power; 3162306a36Sopenharmony_ci u64 latency; 3262306a36Sopenharmony_ci u64 linear; 3362306a36Sopenharmony_ci u64 control; 3462306a36Sopenharmony_ci u64 raw_performace; 3562306a36Sopenharmony_ci char *raw_unit; 3662306a36Sopenharmony_ci int reserved; 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_cistruct int3403_cdev { 4062306a36Sopenharmony_ci struct thermal_cooling_device *cdev; 4162306a36Sopenharmony_ci unsigned long max_state; 4262306a36Sopenharmony_ci}; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_cistruct int3403_priv { 4562306a36Sopenharmony_ci struct platform_device *pdev; 4662306a36Sopenharmony_ci struct acpi_device *adev; 4762306a36Sopenharmony_ci unsigned long long type; 4862306a36Sopenharmony_ci void *priv; 4962306a36Sopenharmony_ci}; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic void int3403_notify(acpi_handle handle, 5262306a36Sopenharmony_ci u32 event, void *data) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct int3403_priv *priv = data; 5562306a36Sopenharmony_ci struct int3403_sensor *obj; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (!priv) 5862306a36Sopenharmony_ci return; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci obj = priv->priv; 6162306a36Sopenharmony_ci if (priv->type != INT3403_TYPE_SENSOR || !obj) 6262306a36Sopenharmony_ci return; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci switch (event) { 6562306a36Sopenharmony_ci case INT3403_PERF_CHANGED_EVENT: 6662306a36Sopenharmony_ci break; 6762306a36Sopenharmony_ci case INT3403_THERMAL_EVENT: 6862306a36Sopenharmony_ci int340x_thermal_zone_device_update(obj->int340x_zone, 6962306a36Sopenharmony_ci THERMAL_TRIP_VIOLATED); 7062306a36Sopenharmony_ci break; 7162306a36Sopenharmony_ci case INT3403_PERF_TRIP_POINT_CHANGED: 7262306a36Sopenharmony_ci int340x_thermal_update_trips(obj->int340x_zone); 7362306a36Sopenharmony_ci int340x_thermal_zone_device_update(obj->int340x_zone, 7462306a36Sopenharmony_ci THERMAL_TRIP_CHANGED); 7562306a36Sopenharmony_ci break; 7662306a36Sopenharmony_ci default: 7762306a36Sopenharmony_ci dev_dbg(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); 7862306a36Sopenharmony_ci break; 7962306a36Sopenharmony_ci } 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic int int3403_sensor_add(struct int3403_priv *priv) 8362306a36Sopenharmony_ci{ 8462306a36Sopenharmony_ci int result = 0; 8562306a36Sopenharmony_ci struct int3403_sensor *obj; 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_ci obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 8862306a36Sopenharmony_ci if (!obj) 8962306a36Sopenharmony_ci return -ENOMEM; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci priv->priv = obj; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL); 9462306a36Sopenharmony_ci if (IS_ERR(obj->int340x_zone)) 9562306a36Sopenharmony_ci return PTR_ERR(obj->int340x_zone); 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci result = acpi_install_notify_handler(priv->adev->handle, 9862306a36Sopenharmony_ci ACPI_DEVICE_NOTIFY, int3403_notify, 9962306a36Sopenharmony_ci (void *)priv); 10062306a36Sopenharmony_ci if (result) 10162306a36Sopenharmony_ci goto err_free_obj; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return 0; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci err_free_obj: 10662306a36Sopenharmony_ci int340x_thermal_zone_remove(obj->int340x_zone); 10762306a36Sopenharmony_ci return result; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_cistatic int int3403_sensor_remove(struct int3403_priv *priv) 11162306a36Sopenharmony_ci{ 11262306a36Sopenharmony_ci struct int3403_sensor *obj = priv->priv; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci acpi_remove_notify_handler(priv->adev->handle, 11562306a36Sopenharmony_ci ACPI_DEVICE_NOTIFY, int3403_notify); 11662306a36Sopenharmony_ci int340x_thermal_zone_remove(obj->int340x_zone); 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci return 0; 11962306a36Sopenharmony_ci} 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/* INT3403 Cooling devices */ 12262306a36Sopenharmony_cistatic int int3403_get_max_state(struct thermal_cooling_device *cdev, 12362306a36Sopenharmony_ci unsigned long *state) 12462306a36Sopenharmony_ci{ 12562306a36Sopenharmony_ci struct int3403_priv *priv = cdev->devdata; 12662306a36Sopenharmony_ci struct int3403_cdev *obj = priv->priv; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ci *state = obj->max_state; 12962306a36Sopenharmony_ci return 0; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic int int3403_get_cur_state(struct thermal_cooling_device *cdev, 13362306a36Sopenharmony_ci unsigned long *state) 13462306a36Sopenharmony_ci{ 13562306a36Sopenharmony_ci struct int3403_priv *priv = cdev->devdata; 13662306a36Sopenharmony_ci unsigned long long level; 13762306a36Sopenharmony_ci acpi_status status; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); 14062306a36Sopenharmony_ci if (ACPI_SUCCESS(status)) { 14162306a36Sopenharmony_ci *state = level; 14262306a36Sopenharmony_ci return 0; 14362306a36Sopenharmony_ci } else 14462306a36Sopenharmony_ci return -EINVAL; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic int 14862306a36Sopenharmony_ciint3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) 14962306a36Sopenharmony_ci{ 15062306a36Sopenharmony_ci struct int3403_priv *priv = cdev->devdata; 15162306a36Sopenharmony_ci acpi_status status; 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); 15462306a36Sopenharmony_ci if (ACPI_SUCCESS(status)) 15562306a36Sopenharmony_ci return 0; 15662306a36Sopenharmony_ci else 15762306a36Sopenharmony_ci return -EINVAL; 15862306a36Sopenharmony_ci} 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_cistatic const struct thermal_cooling_device_ops int3403_cooling_ops = { 16162306a36Sopenharmony_ci .get_max_state = int3403_get_max_state, 16262306a36Sopenharmony_ci .get_cur_state = int3403_get_cur_state, 16362306a36Sopenharmony_ci .set_cur_state = int3403_set_cur_state, 16462306a36Sopenharmony_ci}; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_cistatic int int3403_cdev_add(struct int3403_priv *priv) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci int result = 0; 16962306a36Sopenharmony_ci acpi_status status; 17062306a36Sopenharmony_ci struct int3403_cdev *obj; 17162306a36Sopenharmony_ci struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; 17262306a36Sopenharmony_ci union acpi_object *p; 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ci obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); 17562306a36Sopenharmony_ci if (!obj) 17662306a36Sopenharmony_ci return -ENOMEM; 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); 17962306a36Sopenharmony_ci if (ACPI_FAILURE(status)) 18062306a36Sopenharmony_ci return -ENODEV; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci p = buf.pointer; 18362306a36Sopenharmony_ci if (!p || (p->type != ACPI_TYPE_PACKAGE)) { 18462306a36Sopenharmony_ci pr_warn("Invalid PPSS data\n"); 18562306a36Sopenharmony_ci kfree(buf.pointer); 18662306a36Sopenharmony_ci return -EFAULT; 18762306a36Sopenharmony_ci } 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci priv->priv = obj; 19062306a36Sopenharmony_ci obj->max_state = p->package.count - 1; 19162306a36Sopenharmony_ci obj->cdev = 19262306a36Sopenharmony_ci thermal_cooling_device_register(acpi_device_bid(priv->adev), 19362306a36Sopenharmony_ci priv, &int3403_cooling_ops); 19462306a36Sopenharmony_ci if (IS_ERR(obj->cdev)) 19562306a36Sopenharmony_ci result = PTR_ERR(obj->cdev); 19662306a36Sopenharmony_ci 19762306a36Sopenharmony_ci kfree(buf.pointer); 19862306a36Sopenharmony_ci /* TODO: add ACPI notification support */ 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_ci return result; 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_cistatic int int3403_cdev_remove(struct int3403_priv *priv) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci struct int3403_cdev *obj = priv->priv; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci thermal_cooling_device_unregister(obj->cdev); 20862306a36Sopenharmony_ci return 0; 20962306a36Sopenharmony_ci} 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_cistatic int int3403_add(struct platform_device *pdev) 21262306a36Sopenharmony_ci{ 21362306a36Sopenharmony_ci struct int3403_priv *priv; 21462306a36Sopenharmony_ci int result = 0; 21562306a36Sopenharmony_ci unsigned long long tmp; 21662306a36Sopenharmony_ci acpi_status status; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), 21962306a36Sopenharmony_ci GFP_KERNEL); 22062306a36Sopenharmony_ci if (!priv) 22162306a36Sopenharmony_ci return -ENOMEM; 22262306a36Sopenharmony_ci 22362306a36Sopenharmony_ci priv->pdev = pdev; 22462306a36Sopenharmony_ci priv->adev = ACPI_COMPANION(&(pdev->dev)); 22562306a36Sopenharmony_ci if (!priv->adev) { 22662306a36Sopenharmony_ci result = -EINVAL; 22762306a36Sopenharmony_ci goto err; 22862306a36Sopenharmony_ci } 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci status = acpi_evaluate_integer(priv->adev->handle, "_TMP", 23262306a36Sopenharmony_ci NULL, &tmp); 23362306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 23462306a36Sopenharmony_ci status = acpi_evaluate_integer(priv->adev->handle, "PTYP", 23562306a36Sopenharmony_ci NULL, &priv->type); 23662306a36Sopenharmony_ci if (ACPI_FAILURE(status)) { 23762306a36Sopenharmony_ci result = -EINVAL; 23862306a36Sopenharmony_ci goto err; 23962306a36Sopenharmony_ci } 24062306a36Sopenharmony_ci } else { 24162306a36Sopenharmony_ci priv->type = INT3403_TYPE_SENSOR; 24262306a36Sopenharmony_ci } 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci platform_set_drvdata(pdev, priv); 24562306a36Sopenharmony_ci switch (priv->type) { 24662306a36Sopenharmony_ci case INT3403_TYPE_SENSOR: 24762306a36Sopenharmony_ci result = int3403_sensor_add(priv); 24862306a36Sopenharmony_ci break; 24962306a36Sopenharmony_ci case INT3403_TYPE_CHARGER: 25062306a36Sopenharmony_ci case INT3403_TYPE_BATTERY: 25162306a36Sopenharmony_ci result = int3403_cdev_add(priv); 25262306a36Sopenharmony_ci break; 25362306a36Sopenharmony_ci default: 25462306a36Sopenharmony_ci result = -EINVAL; 25562306a36Sopenharmony_ci } 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci if (result) 25862306a36Sopenharmony_ci goto err; 25962306a36Sopenharmony_ci return result; 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_cierr: 26262306a36Sopenharmony_ci return result; 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic int int3403_remove(struct platform_device *pdev) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci struct int3403_priv *priv = platform_get_drvdata(pdev); 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci switch (priv->type) { 27062306a36Sopenharmony_ci case INT3403_TYPE_SENSOR: 27162306a36Sopenharmony_ci int3403_sensor_remove(priv); 27262306a36Sopenharmony_ci break; 27362306a36Sopenharmony_ci case INT3403_TYPE_CHARGER: 27462306a36Sopenharmony_ci case INT3403_TYPE_BATTERY: 27562306a36Sopenharmony_ci int3403_cdev_remove(priv); 27662306a36Sopenharmony_ci break; 27762306a36Sopenharmony_ci default: 27862306a36Sopenharmony_ci break; 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci return 0; 28262306a36Sopenharmony_ci} 28362306a36Sopenharmony_ci 28462306a36Sopenharmony_cistatic const struct acpi_device_id int3403_device_ids[] = { 28562306a36Sopenharmony_ci {"INT3403", 0}, 28662306a36Sopenharmony_ci {"INTC1043", 0}, 28762306a36Sopenharmony_ci {"INTC1046", 0}, 28862306a36Sopenharmony_ci {"INTC1062", 0}, 28962306a36Sopenharmony_ci {"INTC10A1", 0}, 29062306a36Sopenharmony_ci {"", 0}, 29162306a36Sopenharmony_ci}; 29262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(acpi, int3403_device_ids); 29362306a36Sopenharmony_ci 29462306a36Sopenharmony_cistatic struct platform_driver int3403_driver = { 29562306a36Sopenharmony_ci .probe = int3403_add, 29662306a36Sopenharmony_ci .remove = int3403_remove, 29762306a36Sopenharmony_ci .driver = { 29862306a36Sopenharmony_ci .name = "int3403 thermal", 29962306a36Sopenharmony_ci .acpi_match_table = int3403_device_ids, 30062306a36Sopenharmony_ci }, 30162306a36Sopenharmony_ci}; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_cimodule_platform_driver(int3403_driver); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ciMODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>"); 30662306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 30762306a36Sopenharmony_ciMODULE_DESCRIPTION("ACPI INT3403 thermal driver"); 308