forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
6.5 KiB
305 lines
6.5 KiB
// SPDX-License-Identifier: GPL-2.0-only |
|
/* |
|
* ACPI INT3403 thermal driver |
|
* Copyright (c) 2013, Intel Corporation. |
|
*/ |
|
|
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/init.h> |
|
#include <linux/types.h> |
|
#include <linux/acpi.h> |
|
#include <linux/thermal.h> |
|
#include <linux/platform_device.h> |
|
#include "int340x_thermal_zone.h" |
|
|
|
#define INT3403_TYPE_SENSOR 0x03 |
|
#define INT3403_TYPE_CHARGER 0x0B |
|
#define INT3403_TYPE_BATTERY 0x0C |
|
#define INT3403_PERF_CHANGED_EVENT 0x80 |
|
#define INT3403_PERF_TRIP_POINT_CHANGED 0x81 |
|
#define INT3403_THERMAL_EVENT 0x90 |
|
|
|
/* Preserved structure for future expandbility */ |
|
struct int3403_sensor { |
|
struct int34x_thermal_zone *int340x_zone; |
|
}; |
|
|
|
struct int3403_performance_state { |
|
u64 performance; |
|
u64 power; |
|
u64 latency; |
|
u64 linear; |
|
u64 control; |
|
u64 raw_performace; |
|
char *raw_unit; |
|
int reserved; |
|
}; |
|
|
|
struct int3403_cdev { |
|
struct thermal_cooling_device *cdev; |
|
unsigned long max_state; |
|
}; |
|
|
|
struct int3403_priv { |
|
struct platform_device *pdev; |
|
struct acpi_device *adev; |
|
unsigned long long type; |
|
void *priv; |
|
}; |
|
|
|
static void int3403_notify(acpi_handle handle, |
|
u32 event, void *data) |
|
{ |
|
struct int3403_priv *priv = data; |
|
struct int3403_sensor *obj; |
|
|
|
if (!priv) |
|
return; |
|
|
|
obj = priv->priv; |
|
if (priv->type != INT3403_TYPE_SENSOR || !obj) |
|
return; |
|
|
|
switch (event) { |
|
case INT3403_PERF_CHANGED_EVENT: |
|
break; |
|
case INT3403_THERMAL_EVENT: |
|
int340x_thermal_zone_device_update(obj->int340x_zone, |
|
THERMAL_TRIP_VIOLATED); |
|
break; |
|
case INT3403_PERF_TRIP_POINT_CHANGED: |
|
int340x_thermal_read_trips(obj->int340x_zone); |
|
int340x_thermal_zone_device_update(obj->int340x_zone, |
|
THERMAL_TRIP_CHANGED); |
|
break; |
|
default: |
|
dev_dbg(&priv->pdev->dev, "Unsupported event [0x%x]\n", event); |
|
break; |
|
} |
|
} |
|
|
|
static int int3403_sensor_add(struct int3403_priv *priv) |
|
{ |
|
int result = 0; |
|
struct int3403_sensor *obj; |
|
|
|
obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); |
|
if (!obj) |
|
return -ENOMEM; |
|
|
|
priv->priv = obj; |
|
|
|
obj->int340x_zone = int340x_thermal_zone_add(priv->adev, NULL); |
|
if (IS_ERR(obj->int340x_zone)) |
|
return PTR_ERR(obj->int340x_zone); |
|
|
|
result = acpi_install_notify_handler(priv->adev->handle, |
|
ACPI_DEVICE_NOTIFY, int3403_notify, |
|
(void *)priv); |
|
if (result) |
|
goto err_free_obj; |
|
|
|
return 0; |
|
|
|
err_free_obj: |
|
int340x_thermal_zone_remove(obj->int340x_zone); |
|
return result; |
|
} |
|
|
|
static int int3403_sensor_remove(struct int3403_priv *priv) |
|
{ |
|
struct int3403_sensor *obj = priv->priv; |
|
|
|
acpi_remove_notify_handler(priv->adev->handle, |
|
ACPI_DEVICE_NOTIFY, int3403_notify); |
|
int340x_thermal_zone_remove(obj->int340x_zone); |
|
|
|
return 0; |
|
} |
|
|
|
/* INT3403 Cooling devices */ |
|
static int int3403_get_max_state(struct thermal_cooling_device *cdev, |
|
unsigned long *state) |
|
{ |
|
struct int3403_priv *priv = cdev->devdata; |
|
struct int3403_cdev *obj = priv->priv; |
|
|
|
*state = obj->max_state; |
|
return 0; |
|
} |
|
|
|
static int int3403_get_cur_state(struct thermal_cooling_device *cdev, |
|
unsigned long *state) |
|
{ |
|
struct int3403_priv *priv = cdev->devdata; |
|
unsigned long long level; |
|
acpi_status status; |
|
|
|
status = acpi_evaluate_integer(priv->adev->handle, "PPPC", NULL, &level); |
|
if (ACPI_SUCCESS(status)) { |
|
*state = level; |
|
return 0; |
|
} else |
|
return -EINVAL; |
|
} |
|
|
|
static int |
|
int3403_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) |
|
{ |
|
struct int3403_priv *priv = cdev->devdata; |
|
acpi_status status; |
|
|
|
status = acpi_execute_simple_method(priv->adev->handle, "SPPC", state); |
|
if (ACPI_SUCCESS(status)) |
|
return 0; |
|
else |
|
return -EINVAL; |
|
} |
|
|
|
static const struct thermal_cooling_device_ops int3403_cooling_ops = { |
|
.get_max_state = int3403_get_max_state, |
|
.get_cur_state = int3403_get_cur_state, |
|
.set_cur_state = int3403_set_cur_state, |
|
}; |
|
|
|
static int int3403_cdev_add(struct int3403_priv *priv) |
|
{ |
|
int result = 0; |
|
acpi_status status; |
|
struct int3403_cdev *obj; |
|
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; |
|
union acpi_object *p; |
|
|
|
obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL); |
|
if (!obj) |
|
return -ENOMEM; |
|
|
|
status = acpi_evaluate_object(priv->adev->handle, "PPSS", NULL, &buf); |
|
if (ACPI_FAILURE(status)) |
|
return -ENODEV; |
|
|
|
p = buf.pointer; |
|
if (!p || (p->type != ACPI_TYPE_PACKAGE)) { |
|
pr_warn("Invalid PPSS data\n"); |
|
kfree(buf.pointer); |
|
return -EFAULT; |
|
} |
|
|
|
priv->priv = obj; |
|
obj->max_state = p->package.count - 1; |
|
obj->cdev = |
|
thermal_cooling_device_register(acpi_device_bid(priv->adev), |
|
priv, &int3403_cooling_ops); |
|
if (IS_ERR(obj->cdev)) |
|
result = PTR_ERR(obj->cdev); |
|
|
|
kfree(buf.pointer); |
|
/* TODO: add ACPI notification support */ |
|
|
|
return result; |
|
} |
|
|
|
static int int3403_cdev_remove(struct int3403_priv *priv) |
|
{ |
|
struct int3403_cdev *obj = priv->priv; |
|
|
|
thermal_cooling_device_unregister(obj->cdev); |
|
return 0; |
|
} |
|
|
|
static int int3403_add(struct platform_device *pdev) |
|
{ |
|
struct int3403_priv *priv; |
|
int result = 0; |
|
unsigned long long tmp; |
|
acpi_status status; |
|
|
|
priv = devm_kzalloc(&pdev->dev, sizeof(struct int3403_priv), |
|
GFP_KERNEL); |
|
if (!priv) |
|
return -ENOMEM; |
|
|
|
priv->pdev = pdev; |
|
priv->adev = ACPI_COMPANION(&(pdev->dev)); |
|
if (!priv->adev) { |
|
result = -EINVAL; |
|
goto err; |
|
} |
|
|
|
|
|
status = acpi_evaluate_integer(priv->adev->handle, "_TMP", |
|
NULL, &tmp); |
|
if (ACPI_FAILURE(status)) { |
|
status = acpi_evaluate_integer(priv->adev->handle, "PTYP", |
|
NULL, &priv->type); |
|
if (ACPI_FAILURE(status)) { |
|
result = -EINVAL; |
|
goto err; |
|
} |
|
} else { |
|
priv->type = INT3403_TYPE_SENSOR; |
|
} |
|
|
|
platform_set_drvdata(pdev, priv); |
|
switch (priv->type) { |
|
case INT3403_TYPE_SENSOR: |
|
result = int3403_sensor_add(priv); |
|
break; |
|
case INT3403_TYPE_CHARGER: |
|
case INT3403_TYPE_BATTERY: |
|
result = int3403_cdev_add(priv); |
|
break; |
|
default: |
|
result = -EINVAL; |
|
} |
|
|
|
if (result) |
|
goto err; |
|
return result; |
|
|
|
err: |
|
return result; |
|
} |
|
|
|
static int int3403_remove(struct platform_device *pdev) |
|
{ |
|
struct int3403_priv *priv = platform_get_drvdata(pdev); |
|
|
|
switch (priv->type) { |
|
case INT3403_TYPE_SENSOR: |
|
int3403_sensor_remove(priv); |
|
break; |
|
case INT3403_TYPE_CHARGER: |
|
case INT3403_TYPE_BATTERY: |
|
int3403_cdev_remove(priv); |
|
break; |
|
default: |
|
break; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static const struct acpi_device_id int3403_device_ids[] = { |
|
{"INT3403", 0}, |
|
{"INTC1043", 0}, |
|
{"INTC1046", 0}, |
|
{"", 0}, |
|
}; |
|
MODULE_DEVICE_TABLE(acpi, int3403_device_ids); |
|
|
|
static struct platform_driver int3403_driver = { |
|
.probe = int3403_add, |
|
.remove = int3403_remove, |
|
.driver = { |
|
.name = "int3403 thermal", |
|
.acpi_match_table = int3403_device_ids, |
|
}, |
|
}; |
|
|
|
module_platform_driver(int3403_driver); |
|
|
|
MODULE_AUTHOR("Srinivas Pandruvada <[email protected]>"); |
|
MODULE_LICENSE("GPL v2"); |
|
MODULE_DESCRIPTION("ACPI INT3403 thermal driver");
|
|
|