Commit 6ceaf58a authored by Zhang Rui's avatar Zhang Rui

Merge branch 'int340x-thermal' of .git into next

parents 9ceaa81e d8054749
......@@ -144,7 +144,7 @@ config ACPI_VIDEO
config ACPI_FAN
tristate "Fan"
select THERMAL
depends on THERMAL
default y
help
This driver supports ACPI fan devices, allowing user-mode
......
......@@ -43,6 +43,7 @@ acpi-y += pci_root.o pci_link.o pci_irq.o
acpi-y += acpi_lpss.o
acpi-y += acpi_platform.o
acpi-y += acpi_pnp.o
acpi-y += int340x_thermal.o
acpi-y += power.o
acpi-y += event.o
acpi-y += sysfs.o
......
......@@ -113,3 +113,4 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev)
kfree(resources);
return pdev;
}
EXPORT_SYMBOL_GPL(acpi_create_platform_device);
......@@ -343,6 +343,7 @@ int acpi_device_update_power(struct acpi_device *device, int *state_p)
return 0;
}
EXPORT_SYMBOL_GPL(acpi_device_update_power);
int acpi_bus_update_power(acpi_handle handle, int *state_p)
{
......
......@@ -30,24 +30,19 @@
#include <asm/uaccess.h>
#include <linux/thermal.h>
#include <linux/acpi.h>
#define PREFIX "ACPI: "
#define ACPI_FAN_CLASS "fan"
#define ACPI_FAN_FILE_STATE "state"
#define _COMPONENT ACPI_FAN_COMPONENT
ACPI_MODULE_NAME("fan");
#include <linux/platform_device.h>
#include <linux/sort.h>
MODULE_AUTHOR("Paul Diefenbaugh");
MODULE_DESCRIPTION("ACPI Fan Driver");
MODULE_LICENSE("GPL");
static int acpi_fan_add(struct acpi_device *device);
static int acpi_fan_remove(struct acpi_device *device);
static int acpi_fan_probe(struct platform_device *pdev);
static int acpi_fan_remove(struct platform_device *pdev);
static const struct acpi_device_id fan_device_ids[] = {
{"PNP0C0B", 0},
{"INT3404", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, fan_device_ids);
......@@ -66,37 +61,100 @@ static struct dev_pm_ops acpi_fan_pm = {
#define FAN_PM_OPS_PTR NULL
#endif
static struct acpi_driver acpi_fan_driver = {
.name = "fan",
.class = ACPI_FAN_CLASS,
.ids = fan_device_ids,
.ops = {
.add = acpi_fan_add,
.remove = acpi_fan_remove,
},
.drv.pm = FAN_PM_OPS_PTR,
struct acpi_fan_fps {
u64 control;
u64 trip_point;
u64 speed;
u64 noise_level;
u64 power;
};
struct acpi_fan_fif {
u64 revision;
u64 fine_grain_ctrl;
u64 step_size;
u64 low_speed_notification;
};
struct acpi_fan {
bool acpi4;
struct acpi_fan_fif fif;
struct acpi_fan_fps *fps;
int fps_count;
struct thermal_cooling_device *cdev;
};
static struct platform_driver acpi_fan_driver = {
.probe = acpi_fan_probe,
.remove = acpi_fan_remove,
.driver = {
.name = "acpi-fan",
.acpi_match_table = fan_device_ids,
.pm = FAN_PM_OPS_PTR,
},
};
/* thermal cooling device callbacks */
static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long
*state)
{
/* ACPI fan device only support two states: ON/OFF */
*state = 1;
struct acpi_device *device = cdev->devdata;
struct acpi_fan *fan = acpi_driver_data(device);
if (fan->acpi4)
*state = fan->fps_count - 1;
else
*state = 1;
return 0;
}
static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long
*state)
static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_fan *fan = acpi_driver_data(device);
union acpi_object *obj;
acpi_status status;
int control, i;
status = acpi_evaluate_object(device->handle, "_FST", NULL, &buffer);
if (ACPI_FAILURE(status)) {
dev_err(&device->dev, "Get fan state failed\n");
return status;
}
obj = buffer.pointer;
if (!obj || obj->type != ACPI_TYPE_PACKAGE ||
obj->package.count != 3 ||
obj->package.elements[1].type != ACPI_TYPE_INTEGER) {
dev_err(&device->dev, "Invalid _FST data\n");
status = -EINVAL;
goto err;
}
control = obj->package.elements[1].integer.value;
for (i = 0; i < fan->fps_count; i++) {
if (control == fan->fps[i].control)
break;
}
if (i == fan->fps_count) {
dev_dbg(&device->dev, "Invalid control value returned\n");
status = -EINVAL;
goto err;
}
*state = i;
err:
kfree(obj);
return status;
}
static int fan_get_state(struct acpi_device *device, unsigned long *state)
{
struct acpi_device *device = cdev->devdata;
int result;
int acpi_state = ACPI_STATE_D0;
if (!device)
return -EINVAL;
result = acpi_bus_update_power(device->handle, &acpi_state);
result = acpi_device_update_power(device, &acpi_state);
if (result)
return result;
......@@ -105,21 +163,57 @@ static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long
return 0;
}
static int
fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long
*state)
{
struct acpi_device *device = cdev->devdata;
int result;
struct acpi_fan *fan = acpi_driver_data(device);
if (!device || (state != 0 && state != 1))
if (fan->acpi4)
return fan_get_state_acpi4(device, state);
else
return fan_get_state(device, state);
}
static int fan_set_state(struct acpi_device *device, unsigned long state)
{
if (state != 0 && state != 1)
return -EINVAL;
result = acpi_bus_set_power(device->handle,
state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD);
return acpi_device_set_power(device,
state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD);
}
return result;
static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state)
{
struct acpi_fan *fan = acpi_driver_data(device);
acpi_status status;
if (state >= fan->fps_count)
return -EINVAL;
status = acpi_execute_simple_method(device->handle, "_FSL",
fan->fps[state].control);
if (ACPI_FAILURE(status)) {
dev_dbg(&device->dev, "Failed to set state by _FSL\n");
return status;
}
return 0;
}
static int
fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state)
{
struct acpi_device *device = cdev->devdata;
struct acpi_fan *fan = acpi_driver_data(device);
if (fan->acpi4)
return fan_set_state_acpi4(device, state);
else
return fan_set_state(device, state);
}
static const struct thermal_cooling_device_ops fan_cooling_ops = {
.get_max_state = fan_get_max_state,
.get_cur_state = fan_get_cur_state,
......@@ -130,21 +224,125 @@ static const struct thermal_cooling_device_ops fan_cooling_ops = {
Driver Interface
-------------------------------------------------------------------------- */
static int acpi_fan_add(struct acpi_device *device)
static bool acpi_fan_is_acpi4(struct acpi_device *device)
{
int result = 0;
struct thermal_cooling_device *cdev;
return acpi_has_method(device->handle, "_FIF") &&
acpi_has_method(device->handle, "_FPS") &&
acpi_has_method(device->handle, "_FSL") &&
acpi_has_method(device->handle, "_FST");
}
if (!device)
return -EINVAL;
static int acpi_fan_get_fif(struct acpi_device *device)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_fan *fan = acpi_driver_data(device);
struct acpi_buffer format = { sizeof("NNNN"), "NNNN" };
struct acpi_buffer fif = { sizeof(fan->fif), &fan->fif };
union acpi_object *obj;
acpi_status status;
status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer);
if (ACPI_FAILURE(status))
return status;
obj = buffer.pointer;
if (!obj || obj->type != ACPI_TYPE_PACKAGE) {
dev_err(&device->dev, "Invalid _FIF data\n");
status = -EINVAL;
goto err;
}
strcpy(acpi_device_name(device), "Fan");
strcpy(acpi_device_class(device), ACPI_FAN_CLASS);
status = acpi_extract_package(obj, &format, &fif);
if (ACPI_FAILURE(status)) {
dev_err(&device->dev, "Invalid _FIF element\n");
status = -EINVAL;
}
result = acpi_bus_update_power(device->handle, NULL);
if (result) {
printk(KERN_ERR PREFIX "Setting initial power state\n");
goto end;
err:
kfree(obj);
return status;
}
static int acpi_fan_speed_cmp(const void *a, const void *b)
{
const struct acpi_fan_fps *fps1 = a;
const struct acpi_fan_fps *fps2 = b;
return fps1->speed - fps2->speed;
}
static int acpi_fan_get_fps(struct acpi_device *device)
{
struct acpi_fan *fan = acpi_driver_data(device);
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
acpi_status status;
int i;
status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer);
if (ACPI_FAILURE(status))
return status;
obj = buffer.pointer;
if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) {
dev_err(&device->dev, "Invalid _FPS data\n");
status = -EINVAL;
goto err;
}
fan->fps_count = obj->package.count - 1; /* minus revision field */
fan->fps = devm_kzalloc(&device->dev,
fan->fps_count * sizeof(struct acpi_fan_fps),
GFP_KERNEL);
if (!fan->fps) {
dev_err(&device->dev, "Not enough memory\n");
status = -ENOMEM;
goto err;
}
for (i = 0; i < fan->fps_count; i++) {
struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" };
struct acpi_buffer fps = { sizeof(fan->fps[i]), &fan->fps[i] };
status = acpi_extract_package(&obj->package.elements[i + 1],
&format, &fps);
if (ACPI_FAILURE(status)) {
dev_err(&device->dev, "Invalid _FPS element\n");
break;
}
}
/* sort the state array according to fan speed in increase order */
sort(fan->fps, fan->fps_count, sizeof(*fan->fps),
acpi_fan_speed_cmp, NULL);
err:
kfree(obj);
return status;
}
static int acpi_fan_probe(struct platform_device *pdev)
{
int result = 0;
struct thermal_cooling_device *cdev;
struct acpi_fan *fan;
struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
fan = devm_kzalloc(&pdev->dev, sizeof(*fan), GFP_KERNEL);
if (!fan) {
dev_err(&device->dev, "No memory for fan\n");
return -ENOMEM;
}
device->driver_data = fan;
platform_set_drvdata(pdev, fan);
if (acpi_fan_is_acpi4(device)) {
if (acpi_fan_get_fif(device) || acpi_fan_get_fps(device))
goto end;
fan->acpi4 = true;
} else {
result = acpi_device_update_power(device, NULL);
if (result) {
dev_err(&device->dev, "Setting initial power state\n");
goto end;
}
}
cdev = thermal_cooling_device_register("Fan", device,
......@@ -154,45 +352,34 @@ static int acpi_fan_add(struct acpi_device *device)
goto end;
}
dev_dbg(&device->dev, "registered as cooling_device%d\n", cdev->id);
dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id);
device->driver_data = cdev;
result = sysfs_create_link(&device->dev.kobj,
fan->cdev = cdev;
result = sysfs_create_link(&pdev->dev.kobj,
&cdev->device.kobj,
"thermal_cooling");
if (result)
dev_err(&device->dev, "Failed to create sysfs link "
dev_err(&pdev->dev, "Failed to create sysfs link "
"'thermal_cooling'\n");
result = sysfs_create_link(&cdev->device.kobj,
&device->dev.kobj,
&pdev->dev.kobj,
"device");
if (result)
dev_err(&device->dev, "Failed to create sysfs link "
dev_err(&pdev->dev, "Failed to create sysfs link "
"'device'\n");
printk(KERN_INFO PREFIX "%s [%s] (%s)\n",
acpi_device_name(device), acpi_device_bid(device),
!device->power.state ? "on" : "off");
end:
return result;
}
static int acpi_fan_remove(struct acpi_device *device)
static int acpi_fan_remove(struct platform_device *pdev)
{
struct thermal_cooling_device *cdev;
if (!device)
return -EINVAL;
cdev = acpi_driver_data(device);
if (!cdev)
return -EINVAL;
struct acpi_fan *fan = platform_get_drvdata(pdev);
sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
sysfs_remove_link(&cdev->device.kobj, "device");
thermal_cooling_device_unregister(cdev);
sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling");
sysfs_remove_link(&fan->cdev->device.kobj, "device");
thermal_cooling_device_unregister(fan->cdev);
return 0;
}
......@@ -200,10 +387,11 @@ static int acpi_fan_remove(struct acpi_device *device)
#ifdef CONFIG_PM_SLEEP
static int acpi_fan_suspend(struct device *dev)
{
if (!dev)
return -EINVAL;
struct acpi_fan *fan = dev_get_drvdata(dev);
if (fan->acpi4)
return 0;
acpi_bus_set_power(to_acpi_device(dev)->handle, ACPI_STATE_D0);
acpi_device_set_power(ACPI_COMPANION(dev), ACPI_STATE_D0);
return AE_OK;
}
......@@ -211,16 +399,17 @@ static int acpi_fan_suspend(struct device *dev)
static int acpi_fan_resume(struct device *dev)
{
int result;
struct acpi_fan *fan = dev_get_drvdata(dev);
if (!dev)
return -EINVAL;
if (fan->acpi4)
return 0;
result = acpi_bus_update_power(to_acpi_device(dev)->handle, NULL);
result = acpi_device_update_power(ACPI_COMPANION(dev), NULL);
if (result)
printk(KERN_ERR PREFIX "Error updating fan power state\n");
dev_err(dev, "Error updating fan power state\n");
return result;
}
#endif
module_acpi_driver(acpi_fan_driver);
module_platform_driver(acpi_fan_driver);
/*
* ACPI support for int340x thermal drivers
*
* Copyright (C) 2014, Intel Corporation
* Authors: Zhang Rui <rui.zhang@intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/acpi.h>
#include <linux/module.h>
#include "internal.h"
#define DO_ENUMERATION 0x01
static const struct acpi_device_id int340x_thermal_device_ids[] = {
{"INT3400", DO_ENUMERATION },
{"INT3401"},
{"INT3402"},
{"INT3403"},
{"INT3404"},
{"INT3406"},
{"INT3407"},
{"INT3408"},
{"INT3409"},
{"INT340A"},
{"INT340B"},
{""},
};
static int int340x_thermal_handler_attach(struct acpi_device *adev,
const struct acpi_device_id *id)
{
#if defined(CONFIG_INT340X_THERMAL) || defined(CONFIG_INT340X_THERMAL_MODULE)
if (id->driver_data == DO_ENUMERATION)
acpi_create_platform_device(adev);
#endif
return 1;
}
static struct acpi_scan_handler int340x_thermal_handler = {
.ids = int340x_thermal_device_ids,
.attach = int340x_thermal_handler_attach,
};
void __init acpi_int340x_thermal_init(void)
{
acpi_scan_add_handler(&int340x_thermal_handler);
}
......@@ -31,6 +31,7 @@ void acpi_pci_link_init(void);
void acpi_processor_init(void);
void acpi_platform_init(void);
void acpi_pnp_init(void);
void acpi_int340x_thermal_init(void);
int acpi_sysfs_init(void);
void acpi_container_init(void);
void acpi_memory_hotplug_init(void);
......@@ -103,8 +104,6 @@ int acpi_power_get_inferred_state(struct acpi_device *device, int *state);
int acpi_power_on_resources(struct acpi_device *device, int state);
int acpi_power_transition(struct acpi_device *device, int state);
int acpi_device_update_power(struct acpi_device *device, int *state_p);
int acpi_wakeup_device_init(void);
#ifdef CONFIG_ARCH_MIGHT_HAVE_ACPI_PDC
......@@ -167,13 +166,6 @@ static inline int suspend_nvs_save(void) { return 0; }
static inline void suspend_nvs_restore(void) {}
#endif
/*--------------------------------------------------------------------------
Platform bus support
-------------------------------------------------------------------------- */
struct platform_device;
struct platform_device *acpi_create_platform_device(struct acpi_device *adev);
/*--------------------------------------------------------------------------
Video
-------------------------------------------------------------------------- */
......
......@@ -2306,6 +2306,7 @@ int __init acpi_scan_init(void)
acpi_container_init();
acpi_memory_hotplug_init();
acpi_pnp_init();
acpi_int340x_thermal_init();
mutex_lock(&acpi_scan_lock);
/*
......
......@@ -528,7 +528,6 @@ static void acpi_thermal_check(void *data)
}
/* sys I/F for generic thermal sysfs support */
#define KELVIN_TO_MILLICELSIUS(t, off) (((t) - (off)) * 100)
static int thermal_get_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
......@@ -543,7 +542,8 @@ static int thermal_get_temp(struct thermal_zone_device *thermal,
if (result)
return result;
*temp = KELVIN_TO_MILLICELSIUS(tz->temperature, tz->kelvin_offset);
*temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(tz->temperature,
tz->kelvin_offset);
return 0;
}
......@@ -647,7 +647,7 @@ static int thermal_get_trip_temp(struct thermal_zone_device *thermal,
if (tz->trips.critical.flags.valid) {
if (!trip) {
*temp = KELVIN_TO_MILLICELSIUS(
*temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(
tz->trips.critical.temperature,
tz->kelvin_offset);
return 0;
......@@ -657,7 +657,7 @@ static int thermal_get_trip_temp(struct thermal_zone_device *thermal,
if (tz->trips.hot.flags.valid) {
if (!trip) {
*temp = KELVIN_TO_MILLICELSIUS(
*temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(
tz->trips.hot.temperature,
tz->kelvin_offset);
return 0;
......@@ -667,7 +667,7 @@ static int thermal_get_trip_temp(struct thermal_zone_device *thermal,
if (tz->trips.passive.flags.valid) {
if (!trip) {
*temp = KELVIN_TO_MILLICELSIUS(
*temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(
tz->trips.passive.temperature,
tz->kelvin_offset);
return 0;
......@@ -678,7 +678,7 @@ static int thermal_get_trip_temp(struct thermal_zone_device *thermal,
for (i = 0; i < ACPI_THERMAL_MAX_ACTIVE &&
tz->trips.active[i].flags.valid; i++) {
if (!trip) {
*temp = KELVIN_TO_MILLICELSIUS(
*temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(
tz->trips.active[i].temperature,
tz->kelvin_offset);
return 0;
......@@ -694,7 +694,7 @@ static int thermal_get_crit_temp(struct thermal_zone_device *thermal,
struct acpi_thermal *tz = thermal->devdata;
if (tz->trips.critical.flags.valid) {
*temperature = KELVIN_TO_MILLICELSIUS(
*temperature = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(
tz->trips.critical.temperature,
tz->kelvin_offset);
return 0;
......@@ -714,8 +714,8 @@ static int thermal_get_trend(struct thermal_zone_device *thermal,
if (type == THERMAL_TRIP_ACTIVE) {
unsigned long trip_temp;
unsigned long temp = KELVIN_TO_MILLICELSIUS(tz->temperature,
tz->kelvin_offset);
unsigned long temp = DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(
tz->temperature, tz->kelvin_offset);
if (thermal_get_trip_temp(thermal, trip, &trip_temp))
return -EINVAL;
......
......@@ -149,6 +149,21 @@ acpi_extract_package(union acpi_object *package,
break;
}
break;
case ACPI_TYPE_LOCAL_REFERENCE:
switch (format_string[i]) {
case 'R':
size_required += sizeof(void *);
tail_offset += sizeof(void *);
break;
default:
printk(KERN_WARNING PREFIX "Invalid package element"
" [%d] got reference,"
" expecting [%c]\n",
i, format_string[i]);
return AE_BAD_DATA;
break;
}
break;
case ACPI_TYPE_PACKAGE:
default:
......@@ -247,7 +262,18 @@ acpi_extract_package(union acpi_object *package,
break;
}
break;
case ACPI_TYPE_LOCAL_REFERENCE:
switch (format_string[i]) {
case 'R':
*(void **)head =
(void *)element->reference.handle;
head += sizeof(void *);
break;
default:
/* Should never get here */
break;
}
break;
case ACPI_TYPE_PACKAGE:
/* TBD: handle nested packages... */
default:
......
......@@ -217,21 +217,6 @@ config X86_PKG_TEMP_THERMAL
two trip points which can be set by user to get notifications via thermal
notification methods.
config ACPI_INT3403_THERMAL
tristate "ACPI INT3403 thermal driver"
depends on X86 && ACPI
help
Newer laptops and tablets that use ACPI may have thermal sensors
outside the core CPU/SOC for thermal safety reasons. These
temperature sensors are also exposed for the OS to use via the so
called INT3403 ACPI object. This driver will, on devices that have
such sensors, expose the temperature information from these sensors
to userspace via the normal thermal framework. This means that a wide
range of applications and GUI widgets can show this information to
the user or use this information for making decisions. For example,
the Intel Thermal Daemon can use this information to allow the user
to select his laptop to run without turning on the fans.
config INTEL_SOC_DTS_THERMAL
tristate "Intel SoCs DTS thermal driver"
depends on X86 && IOSF_MBI
......@@ -244,6 +229,30 @@ config INTEL_SOC_DTS_THERMAL
notification methods.The other trip is a critical trip point, which
was set by the driver based on the TJ MAX temperature.
config INT340X_THERMAL
tristate "ACPI INT340X thermal drivers"
depends on X86 && ACPI
select THERMAL_GOV_USER_SPACE
select ACPI_THERMAL_REL
select ACPI_FAN
help
Newer laptops and tablets that use ACPI may have thermal sensors and
other devices with thermal control capabilities outside the core
CPU/SOC, for thermal safety reasons.
They are exposed for the OS to use via the INT3400 ACPI device object
as the master, and INT3401~INT340B ACPI device objects as the slaves.
Enable this to expose the temperature information and cooling ability
from these objects to userspace via the normal thermal framework.
This means that a wide range of applications and GUI widgets can show
the information to the user or use this information for making
decisions. For example, the Intel Thermal Daemon can use this
information to allow the user to select his laptop to run without
turning on the fans.
config ACPI_THERMAL_REL
tristate
depends on ACPI
menu "Texas Instruments thermal drivers"
source "drivers/thermal/ti-soc-thermal/Kconfig"
endmenu
......
......@@ -32,5 +32,5 @@ obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o
obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o
obj-$(CONFIG_TI_SOC_THERMAL) += ti-soc-thermal/
obj-$(CONFIG_ACPI_INT3403_THERMAL) += int3403_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
obj-$(CONFIG_ST_THERMAL) += st/
obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o
obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o
/* acpi_thermal_rel.c driver for exporting ACPI thermal relationship
*
* Copyright (c) 2014 Intel Corp
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
*/
/*
* Two functionalities included:
* 1. Export _TRT, _ART, via misc device interface to the userspace.
* 2. Provide parsing result to kernel drivers
*
*/
#include <linux/init.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/acpi.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include "acpi_thermal_rel.h"
static acpi_handle acpi_thermal_rel_handle;
static DEFINE_SPINLOCK(acpi_thermal_rel_chrdev_lock);
static int acpi_thermal_rel_chrdev_count; /* #times opened */
static int acpi_thermal_rel_chrdev_exclu; /* already open exclusive? */
static int acpi_thermal_rel_open(struct inode *inode, struct file *file)
{
spin_lock(&acpi_thermal_rel_chrdev_lock);
if (acpi_thermal_rel_chrdev_exclu ||
(acpi_thermal_rel_chrdev_count && (file->f_flags & O_EXCL))) {
spin_unlock(&acpi_thermal_rel_chrdev_lock);
return -EBUSY;
}
if (file->f_flags & O_EXCL)
acpi_thermal_rel_chrdev_exclu = 1;
acpi_thermal_rel_chrdev_count++;
spin_unlock(&acpi_thermal_rel_chrdev_lock);
return nonseekable_open(inode, file);
}
static int acpi_thermal_rel_release(struct inode *inode, struct file *file)
{
spin_lock(&acpi_thermal_rel_chrdev_lock);
acpi_thermal_rel_chrdev_count--;
acpi_thermal_rel_chrdev_exclu = 0;
spin_unlock(&acpi_thermal_rel_chrdev_lock);
return 0;
}
/**
* acpi_parse_trt - Thermal Relationship Table _TRT for passive cooling
*
* @handle: ACPI handle of the device contains _TRT
* @art_count: the number of valid entries resulted from parsing _TRT
* @artp: pointer to pointer of array of art entries in parsing result
* @create_dev: whether to create platform devices for target and source
*
*/
int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trtp,
bool create_dev)
{
acpi_status status;
int result = 0;
int i;
int nr_bad_entries = 0;
struct trt *trts;
struct acpi_device *adev;
union acpi_object *p;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer element = { 0, NULL };
struct acpi_buffer trt_format = { sizeof("RRNNNNNN"), "RRNNNNNN" };
if (!acpi_has_method(handle, "_TRT"))
return 0;
status = acpi_evaluate_object(handle, "_TRT", NULL, &buffer);
if (ACPI_FAILURE(status))
return -ENODEV;
p = buffer.pointer;
if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
pr_err("Invalid _TRT data\n");
result = -EFAULT;
goto end;
}
*trt_count = p->package.count;
trts = kzalloc(*trt_count * sizeof(struct trt), GFP_KERNEL);
if (!trts) {
result = -ENOMEM;
goto end;
}
for (i = 0; i < *trt_count; i++) {
struct trt *trt = &trts[i - nr_bad_entries];
element.length = sizeof(struct trt);
element.pointer = trt;
status = acpi_extract_package(&(p->package.elements[i]),
&trt_format, &element);
if (ACPI_FAILURE(status)) {
nr_bad_entries++;
pr_warn("_TRT package %d is invalid, ignored\n", i);
continue;
}
if (!create_dev)
continue;
result = acpi_bus_get_device(trt->source, &adev);
if (!result)
acpi_create_platform_device(adev);
else
pr_warn("Failed to get source ACPI device\n");
result = acpi_bus_get_device(trt->target, &adev);
if (!result)
acpi_create_platform_device(adev);
else
pr_warn("Failed to get target ACPI device\n");
}
*trtp = trts;
/* don't count bad entries */
*trt_count -= nr_bad_entries;
end:
kfree(buffer.pointer);
return result;
}
EXPORT_SYMBOL(acpi_parse_trt);
/**
* acpi_parse_art - Parse Active Relationship Table _ART
*
* @handle: ACPI handle of the device contains _ART
* @art_count: the number of valid entries resulted from parsing _ART
* @artp: pointer to pointer of array of art entries in parsing result
* @create_dev: whether to create platform devices for target and source
*
*/
int acpi_parse_art(acpi_handle handle, int *art_count, struct art **artp,
bool create_dev)
{
acpi_status status;
int result = 0;
int i;
int nr_bad_entries = 0;
struct art *arts;
struct acpi_device *adev;
union acpi_object *p;
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
struct acpi_buffer element = { 0, NULL };
struct acpi_buffer art_format = {
sizeof("RRNNNNNNNNNNN"), "RRNNNNNNNNNNN" };
if (!acpi_has_method(handle, "_ART"))
return 0;
status = acpi_evaluate_object(handle, "_ART", NULL, &buffer);
if (ACPI_FAILURE(status))
return -ENODEV;
p = buffer.pointer;
if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
pr_err("Invalid _ART data\n");
result = -EFAULT;
goto end;
}
/* ignore p->package.elements[0], as this is _ART Revision field */
*art_count = p->package.count - 1;
arts = kzalloc(*art_count * sizeof(struct art), GFP_KERNEL);
if (!arts) {
result = -ENOMEM;
goto end;
}
for (i = 0; i < *art_count; i++) {
struct art *art = &arts[i - nr_bad_entries];
element.length = sizeof(struct art);
element.pointer = art;
status = acpi_extract_package(&(p->package.elements[i + 1]),
&art_format, &element);
if (ACPI_FAILURE(status)) {
pr_warn("_ART package %d is invalid, ignored", i);
nr_bad_entries++;
continue;
}
if (!create_dev)
continue;
if (art->source) {
result = acpi_bus_get_device(art->source, &adev);
if (!result)
acpi_create_platform_device(adev);
else
pr_warn("Failed to get source ACPI device\n");
}
if (art->target) {
result = acpi_bus_get_device(art->target, &adev);
if (!result)
acpi_create_platform_device(adev);
else
pr_warn("Failed to get source ACPI device\n");
}
}
*artp = arts;
/* don't count bad entries */
*art_count -= nr_bad_entries;
end:
kfree(buffer.pointer);
return result;
}
EXPORT_SYMBOL(acpi_parse_art);
/* get device name from acpi handle */
static void get_single_name(acpi_handle handle, char *name)
{
struct acpi_buffer buffer = {ACPI_ALLOCATE_BUFFER};
if (ACPI_FAILURE(acpi_get_name(handle, ACPI_SINGLE_NAME, &buffer)))
pr_warn("Failed get name from handle\n");
else {
memcpy(name, buffer.pointer, ACPI_NAME_SIZE);
kfree(buffer.pointer);
}
}
static int fill_art(char __user *ubuf)
{
int i;
int ret;
int count;
int art_len;
struct art *arts = NULL;
union art_object *art_user;
ret = acpi_parse_art(acpi_thermal_rel_handle, &count, &arts, false);
if (ret)
goto free_art;
art_len = count * sizeof(union art_object);
art_user = kzalloc(art_len, GFP_KERNEL);
if (!art_user) {
ret = -ENOMEM;
goto free_art;
}
/* now fill in user art data */
for (i = 0; i < count; i++) {
/* userspace art needs device name instead of acpi reference */
get_single_name(arts[i].source, art_user[i].source_device);
get_single_name(arts[i].target, art_user[i].target_device);
/* copy the rest int data in addition to source and target */
memcpy(&art_user[i].weight, &arts[i].weight,
sizeof(u64) * (ACPI_NR_ART_ELEMENTS - 2));
}
if (copy_to_user(ubuf, art_user, art_len))
ret = -EFAULT;
kfree(art_user);
free_art:
kfree(arts);
return ret;
}
static int fill_trt(char __user *ubuf)
{
int i;
int ret;
int count;
int trt_len;
struct trt *trts = NULL;
union trt_object *trt_user;
ret = acpi_parse_trt(acpi_thermal_rel_handle, &count, &trts, false);
if (ret)
goto free_trt;
trt_len = count * sizeof(union trt_object);
trt_user = kzalloc(trt_len, GFP_KERNEL);
if (!trt_user) {
ret = -ENOMEM;
goto free_trt;
}
/* now fill in user trt data */
for (i = 0; i < count; i++) {
/* userspace trt needs device name instead of acpi reference */
get_single_name(trts[i].source, trt_user[i].source_device);
get_single_name(trts[i].target, trt_user[i].target_device);
trt_user[i].sample_period = trts[i].sample_period;
trt_user[i].influence = trts[i].influence;
}
if (copy_to_user(ubuf, trt_user, trt_len))
ret = -EFAULT;
kfree(trt_user);
free_trt:
kfree(trts);
return ret;
}
static long acpi_thermal_rel_ioctl(struct file *f, unsigned int cmd,
unsigned long __arg)
{
int ret = 0;
unsigned long length = 0;
unsigned long count = 0;
char __user *arg = (void __user *)__arg;
struct trt *trts;
struct art *arts;
switch (cmd) {
case ACPI_THERMAL_GET_TRT_COUNT:
ret = acpi_parse_trt(acpi_thermal_rel_handle, (int *)&count,
&trts, false);
kfree(trts);
if (!ret)
return put_user(count, (unsigned long __user *)__arg);
return ret;
case ACPI_THERMAL_GET_TRT_LEN:
ret = acpi_parse_trt(acpi_thermal_rel_handle, (int *)&count,
&trts, false);
kfree(trts);
length = count * sizeof(union trt_object);
if (!ret)
return put_user(length, (unsigned long __user *)__arg);
return ret;
case ACPI_THERMAL_GET_TRT:
return fill_trt(arg);
case ACPI_THERMAL_GET_ART_COUNT:
ret = acpi_parse_art(acpi_thermal_rel_handle, (int *)&count,
&arts, false);
kfree(arts);
if (!ret)
return put_user(count, (unsigned long __user *)__arg);
return ret;
case ACPI_THERMAL_GET_ART_LEN:
ret = acpi_parse_art(acpi_thermal_rel_handle, (int *)&count,
&arts, false);
kfree(arts);
length = count * sizeof(union art_object);
if (!ret)
return put_user(length, (unsigned long __user *)__arg);
return ret;
case ACPI_THERMAL_GET_ART:
return fill_art(arg);
default:
return -ENOTTY;
}
}
static const struct file_operations acpi_thermal_rel_fops = {
.owner = THIS_MODULE,
.open = acpi_thermal_rel_open,
.release = acpi_thermal_rel_release,
.unlocked_ioctl = acpi_thermal_rel_ioctl,
.llseek = no_llseek,
};
static struct miscdevice acpi_thermal_rel_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
"acpi_thermal_rel",
&acpi_thermal_rel_fops
};
int acpi_thermal_rel_misc_device_add(acpi_handle handle)
{
acpi_thermal_rel_handle = handle;
return misc_register(&acpi_thermal_rel_misc_device);
}
EXPORT_SYMBOL(acpi_thermal_rel_misc_device_add);
int acpi_thermal_rel_misc_device_remove(acpi_handle handle)
{
misc_deregister(&acpi_thermal_rel_misc_device);
return 0;
}
EXPORT_SYMBOL(acpi_thermal_rel_misc_device_remove);
MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@intel.com");
MODULE_DESCRIPTION("Intel acpi thermal rel misc dev driver");
MODULE_LICENSE("GPL v2");
#ifndef __ACPI_ACPI_THERMAL_H
#define __ACPI_ACPI_THERMAL_H
#include <asm/ioctl.h>
#define ACPI_THERMAL_MAGIC 's'
#define ACPI_THERMAL_GET_TRT_LEN _IOR(ACPI_THERMAL_MAGIC, 1, unsigned long)
#define ACPI_THERMAL_GET_ART_LEN _IOR(ACPI_THERMAL_MAGIC, 2, unsigned long)
#define ACPI_THERMAL_GET_TRT_COUNT _IOR(ACPI_THERMAL_MAGIC, 3, unsigned long)
#define ACPI_THERMAL_GET_ART_COUNT _IOR(ACPI_THERMAL_MAGIC, 4, unsigned long)
#define ACPI_THERMAL_GET_TRT _IOR(ACPI_THERMAL_MAGIC, 5, unsigned long)
#define ACPI_THERMAL_GET_ART _IOR(ACPI_THERMAL_MAGIC, 6, unsigned long)
struct art {
acpi_handle source;
acpi_handle target;
u64 weight;
u64 ac0_max;
u64 ac1_max;
u64 ac2_max;
u64 ac3_max;
u64 ac4_max;
u64 ac5_max;
u64 ac6_max;
u64 ac7_max;
u64 ac8_max;
u64 ac9_max;
} __packed;
struct trt {
acpi_handle source;
acpi_handle target;
u64 influence;
u64 sample_period;
u64 reverved1;
u64 reverved2;
u64 reverved3;
u64 reverved4;
} __packed;
#define ACPI_NR_ART_ELEMENTS 13
/* for usrspace */
union art_object {
struct {
char source_device[8]; /* ACPI single name */
char target_device[8]; /* ACPI single name */
u64 weight;
u64 ac0_max_level;
u64 ac1_max_level;
u64 ac2_max_level;
u64 ac3_max_level;
u64 ac4_max_level;
u64 ac5_max_level;
u64 ac6_max_level;
u64 ac7_max_level;
u64 ac8_max_level;
u64 ac9_max_level;
};
u64 __data[ACPI_NR_ART_ELEMENTS];
};
union trt_object {
struct {
char source_device[8]; /* ACPI single name */
char target_device[8]; /* ACPI single name */
u64 influence;
u64 sample_period;
u64 reserved[4];
};
u64 __data[8];
};
#ifdef __KERNEL__
int acpi_thermal_rel_misc_device_add(acpi_handle handle);
int acpi_thermal_rel_misc_device_remove(acpi_handle handle);
int acpi_parse_art(acpi_handle handle, int *art_count, struct art **arts,
bool create_dev);
int acpi_parse_trt(acpi_handle handle, int *trt_count, struct trt **trts,
bool create_dev);
#endif
#endif /* __ACPI_ACPI_THERMAL_H */
/*
* INT3400 thermal driver
*
* Copyright (C) 2014, Intel Corporation
* Authors: Zhang Rui <rui.zhang@intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <linux/thermal.h>
#include "acpi_thermal_rel.h"
enum int3400_thermal_uuid {
INT3400_THERMAL_PASSIVE_1,
INT3400_THERMAL_PASSIVE_2,
INT3400_THERMAL_ACTIVE,
INT3400_THERMAL_CRITICAL,
INT3400_THERMAL_COOLING_MODE,
INT3400_THERMAL_MAXIMUM_UUID,
};
static u8 *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
"42A441D6-AE6A-462b-A84B-4A8CE79027D3",
"9E04115A-AE87-4D1C-9500-0F3E340BFE75",
"3A95C389-E4B8-4629-A526-C52C88626BAE",
"97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
"16CAF1B7-DD38-40ed-B1C1-1B8A1913D531",
};
struct int3400_thermal_priv {
struct acpi_device *adev;
struct thermal_zone_device *thermal;
int mode;
int art_count;
struct art *arts;
int trt_count;
struct trt *trts;
u8 uuid_bitmap;
int rel_misc_dev_res;
};
static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL};
union acpi_object *obja, *objb;
int i, j;
int result = 0;
acpi_status status;
status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf);
if (ACPI_FAILURE(status))
return -ENODEV;
obja = (union acpi_object *)buf.pointer;
if (obja->type != ACPI_TYPE_PACKAGE) {
result = -EINVAL;
goto end;
}
for (i = 0; i < obja->package.count; i++) {
objb = &obja->package.elements[i];
if (objb->type != ACPI_TYPE_BUFFER) {
result = -EINVAL;
goto end;
}
/* UUID must be 16 bytes */
if (objb->buffer.length != 16) {
result = -EINVAL;
goto end;
}
for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) {
u8 uuid[16];
acpi_str_to_uuid(int3400_thermal_uuids[j], uuid);
if (!strncmp(uuid, objb->buffer.pointer, 16)) {
priv->uuid_bitmap |= (1 << j);
break;
}
}
}
end:
kfree(buf.pointer);
return result;
}
static int int3400_thermal_run_osc(acpi_handle handle,
enum int3400_thermal_uuid uuid, bool enable)
{
u32 ret, buf[2];
acpi_status status;
int result = 0;
struct acpi_osc_context context = {
.uuid_str = int3400_thermal_uuids[uuid],
.rev = 1,
.cap.length = 8,
};
buf[OSC_QUERY_DWORD] = 0;
buf[OSC_SUPPORT_DWORD] = enable;
context.cap.pointer = buf;
status = acpi_run_osc(handle, &context);
if (ACPI_SUCCESS(status)) {
ret = *((u32 *)(context.ret.pointer + 4));
if (ret != enable)
result = -EPERM;
} else
result = -EPERM;
kfree(context.ret.pointer);
return result;
}
static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
unsigned long *temp)
{
*temp = 20 * 1000; /* faked temp sensor with 20C */
return 0;
}
static int int3400_thermal_get_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode *mode)
{
struct int3400_thermal_priv *priv = thermal->devdata;
if (!priv)
return -EINVAL;
*mode = priv->mode;
return 0;
}
static int int3400_thermal_set_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode mode)
{
struct int3400_thermal_priv *priv = thermal->devdata;
bool enable;
int result = 0;
if (!priv)
return -EINVAL;
if (mode == THERMAL_DEVICE_ENABLED)
enable = true;
else if (mode == THERMAL_DEVICE_DISABLED)
enable = false;
else
return -EINVAL;
if (enable != priv->mode) {
priv->mode = enable;
/* currently, only PASSIVE COOLING is supported */
result = int3400_thermal_run_osc(priv->adev->handle,
INT3400_THERMAL_PASSIVE_1, enable);
}
return result;
}
static struct thermal_zone_device_ops int3400_thermal_ops = {
.get_temp = int3400_thermal_get_temp,
};
static struct thermal_zone_params int3400_thermal_params = {
.governor_name = "user_space",
.no_hwmon = true,
};
static int int3400_thermal_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
struct int3400_thermal_priv *priv;
int result;
if (!adev)
return -ENODEV;
priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->adev = adev;
result = int3400_thermal_get_uuids(priv);
if (result)
goto free_priv;
result = acpi_parse_art(priv->adev->handle, &priv->art_count,
&priv->arts, true);
if (result)
goto free_priv;
result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,
&priv->trts, true);
if (result)
goto free_art;
platform_set_drvdata(pdev, priv);
if (priv->uuid_bitmap & 1 << INT3400_THERMAL_PASSIVE_1) {
int3400_thermal_ops.get_mode = int3400_thermal_get_mode;
int3400_thermal_ops.set_mode = int3400_thermal_set_mode;
}
priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
priv, &int3400_thermal_ops,
&int3400_thermal_params, 0, 0);
if (IS_ERR(priv->thermal)) {
result = PTR_ERR(priv->thermal);
goto free_trt;
}
priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
priv->adev->handle);
return 0;
free_trt:
kfree(priv->trts);
free_art:
kfree(priv->arts);
free_priv:
kfree(priv);
return result;
}
static int int3400_thermal_remove(struct platform_device *pdev)
{
struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
if (!priv->rel_misc_dev_res)
acpi_thermal_rel_misc_device_remove(priv->adev->handle);
thermal_zone_device_unregister(priv->thermal);
kfree(priv->trts);
kfree(priv->arts);
kfree(priv);
return 0;
}
static const struct acpi_device_id int3400_thermal_match[] = {
{"INT3400", 0},
{}
};
MODULE_DEVICE_TABLE(acpi, int3400_thermal_match);
static struct platform_driver int3400_thermal_driver = {
.probe = int3400_thermal_probe,
.remove = int3400_thermal_remove,
.driver = {
.name = "int3400 thermal",
.owner = THIS_MODULE,
.acpi_match_table = ACPI_PTR(int3400_thermal_match),
},
};
module_platform_driver(int3400_thermal_driver);
MODULE_DESCRIPTION("INT3400 Thermal driver");
MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
MODULE_LICENSE("GPL");
/*
* INT3402 thermal driver for memory temperature reporting
*
* Copyright (C) 2014, Intel Corporation
* Authors: Aaron Lu <aaron.lu@intel.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <linux/thermal.h>
#define ACPI_ACTIVE_COOLING_MAX_NR 10
struct active_trip {
unsigned long temp;
int id;
bool valid;
};
struct int3402_thermal_data {
unsigned long *aux_trips;
int aux_trip_nr;
unsigned long psv_temp;
int psv_trip_id;
unsigned long crt_temp;
int crt_trip_id;
unsigned long hot_temp;
int hot_trip_id;
struct active_trip act_trips[ACPI_ACTIVE_COOLING_MAX_NR];
acpi_handle *handle;
};
static int int3402_thermal_get_zone_temp(struct thermal_zone_device *zone,
unsigned long *temp)
{
struct int3402_thermal_data *d = zone->devdata;
unsigned long long tmp;
acpi_status status;
status = acpi_evaluate_integer(d->handle, "_TMP", NULL, &tmp);
if (ACPI_FAILURE(status))
return -ENODEV;
/* _TMP returns the temperature in tenths of degrees Kelvin */
*temp = DECI_KELVIN_TO_MILLICELSIUS(tmp);
return 0;
}
static int int3402_thermal_get_trip_temp(struct thermal_zone_device *zone,
int trip, unsigned long *temp)
{
struct int3402_thermal_data *d = zone->devdata;
int i;
if (trip < d->aux_trip_nr)
*temp = d->aux_trips[trip];
else if (trip == d->crt_trip_id)
*temp = d->crt_temp;
else if (trip == d->psv_trip_id)
*temp = d->psv_temp;
else if (trip == d->hot_trip_id)
*temp = d->hot_temp;
else {
for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) {
if (d->act_trips[i].valid &&
d->act_trips[i].id == trip) {
*temp = d->act_trips[i].temp;
break;
}
}
if (i == ACPI_ACTIVE_COOLING_MAX_NR)
return -EINVAL;
}
return 0;
}
static int int3402_thermal_get_trip_type(struct thermal_zone_device *zone,
int trip, enum thermal_trip_type *type)
{
struct int3402_thermal_data *d = zone->devdata;
int i;
if (trip < d->aux_trip_nr)
*type = THERMAL_TRIP_PASSIVE;
else if (trip == d->crt_trip_id)
*type = THERMAL_TRIP_CRITICAL;
else if (trip == d->hot_trip_id)
*type = THERMAL_TRIP_HOT;
else if (trip == d->psv_trip_id)
*type = THERMAL_TRIP_PASSIVE;
else {
for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) {
if (d->act_trips[i].valid &&
d->act_trips[i].id == trip) {
*type = THERMAL_TRIP_ACTIVE;
break;
}
}
if (i == ACPI_ACTIVE_COOLING_MAX_NR)
return -EINVAL;
}
return 0;
}
static int int3402_thermal_set_trip_temp(struct thermal_zone_device *zone, int trip,
unsigned long temp)
{
struct int3402_thermal_data *d = zone->devdata;
acpi_status status;
char name[10];
snprintf(name, sizeof(name), "PAT%d", trip);
status = acpi_execute_simple_method(d->handle, name,
MILLICELSIUS_TO_DECI_KELVIN(temp));
if (ACPI_FAILURE(status))
return -EIO;
d->aux_trips[trip] = temp;
return 0;
}
static struct thermal_zone_device_ops int3402_thermal_zone_ops = {
.get_temp = int3402_thermal_get_zone_temp,
.get_trip_temp = int3402_thermal_get_trip_temp,
.get_trip_type = int3402_thermal_get_trip_type,
.set_trip_temp = int3402_thermal_set_trip_temp,
};
static struct thermal_zone_params int3402_thermal_params = {
.governor_name = "user_space",
.no_hwmon = true,
};
static int int3402_thermal_get_temp(acpi_handle handle, char *name,
unsigned long *temp)
{
unsigned long long r;
acpi_status status;
status = acpi_evaluate_integer(handle, name, NULL, &r);
if (ACPI_FAILURE(status))
return -EIO;
*temp = DECI_KELVIN_TO_MILLICELSIUS(r);
return 0;
}
static int int3402_thermal_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
struct int3402_thermal_data *d;
struct thermal_zone_device *zone;
acpi_status status;
unsigned long long trip_cnt;
int trip_mask = 0, i;
if (!acpi_has_method(adev->handle, "_TMP"))
return -ENODEV;
d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt);
if (ACPI_FAILURE(status))
trip_cnt = 0;
else {
d->aux_trips = devm_kzalloc(&pdev->dev,
sizeof(*d->aux_trips) * trip_cnt, GFP_KERNEL);
if (!d->aux_trips)
return -ENOMEM;
trip_mask = trip_cnt - 1;
d->handle = adev->handle;
d->aux_trip_nr = trip_cnt;
}
d->crt_trip_id = -1;
if (!int3402_thermal_get_temp(adev->handle, "_CRT", &d->crt_temp))
d->crt_trip_id = trip_cnt++;
d->hot_trip_id = -1;
if (!int3402_thermal_get_temp(adev->handle, "_HOT", &d->hot_temp))
d->hot_trip_id = trip_cnt++;
d->psv_trip_id = -1;
if (!int3402_thermal_get_temp(adev->handle, "_PSV", &d->psv_temp))
d->psv_trip_id = trip_cnt++;
for (i = 0; i < ACPI_ACTIVE_COOLING_MAX_NR; i++) {
char name[5] = { '_', 'A', 'C', '0' + i, '\0' };
if (int3402_thermal_get_temp(adev->handle, name,
&d->act_trips[i].temp))
break;
d->act_trips[i].id = trip_cnt++;
d->act_trips[i].valid = true;
}
zone = thermal_zone_device_register(acpi_device_bid(adev), trip_cnt,
trip_mask, d,
&int3402_thermal_zone_ops,
&int3402_thermal_params,
0, 0);
if (IS_ERR(zone))
return PTR_ERR(zone);
platform_set_drvdata(pdev, zone);
return 0;
}
static int int3402_thermal_remove(struct platform_device *pdev)
{
struct thermal_zone_device *zone = platform_get_drvdata(pdev);
thermal_zone_device_unregister(zone);
return 0;
}
static const struct acpi_device_id int3402_thermal_match[] = {
{"INT3402", 0},
{}
};
MODULE_DEVICE_TABLE(acpi, int3402_thermal_match);
static struct platform_driver int3402_thermal_driver = {
.probe = int3402_thermal_probe,
.remove = int3402_thermal_remove,
.driver = {
.name = "int3402 thermal",
.owner = THIS_MODULE,
.acpi_match_table = int3402_thermal_match,
},
};
module_platform_driver(int3402_thermal_driver);
MODULE_DESCRIPTION("INT3402 Thermal driver");
MODULE_LICENSE("GPL");
......@@ -18,8 +18,11 @@
#include <linux/types.h>
#include <linux/acpi.h>
#include <linux/thermal.h>
#include <linux/platform_device.h>
#define INT3403_TYPE_SENSOR 0x03
#define INT3403_TYPE_CHARGER 0x0B
#define INT3403_TYPE_BATTERY 0x0C
#define INT3403_PERF_CHANGED_EVENT 0x80
#define INT3403_THERMAL_EVENT 0x90
......@@ -27,9 +30,6 @@
#define KELVIN_OFFSET 2732
#define MILLI_CELSIUS_TO_DECI_KELVIN(t, off) (((t) / 100) + (off))
#define ACPI_INT3403_CLASS "int3403"
#define ACPI_INT3403_FILE_STATE "state"
struct int3403_sensor {
struct thermal_zone_device *tzone;
unsigned long *thresholds;
......@@ -37,12 +37,37 @@ struct int3403_sensor {
int crit_trip_id;
unsigned long psv_temp;
int psv_trip_id;
};
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 int sys_get_curr_temp(struct thermal_zone_device *tzone,
unsigned long *temp)
{
struct acpi_device *device = tzone->devdata;
struct int3403_priv *priv = tzone->devdata;
struct acpi_device *device = priv->adev;
unsigned long long tmp;
acpi_status status;
......@@ -58,7 +83,8 @@ static int sys_get_curr_temp(struct thermal_zone_device *tzone,
static int sys_get_trip_hyst(struct thermal_zone_device *tzone,
int trip, unsigned long *temp)
{
struct acpi_device *device = tzone->devdata;
struct int3403_priv *priv = tzone->devdata;
struct acpi_device *device = priv->adev;
unsigned long long hyst;
acpi_status status;
......@@ -66,13 +92,7 @@ static int sys_get_trip_hyst(struct thermal_zone_device *tzone,
if (ACPI_FAILURE(status))
return -EIO;
/*
* Thermal hysteresis represents a temperature difference.
* Kelvin and Celsius have same degree size. So the
* conversion here between tenths of degree Kelvin unit
* and Milli-Celsius unit is just to multiply 100.
*/
*temp = hyst * 100;
*temp = DECI_KELVIN_TO_MILLI_CELSIUS(hyst, KELVIN_OFFSET);
return 0;
}
......@@ -80,8 +100,11 @@ static int sys_get_trip_hyst(struct thermal_zone_device *tzone,
static int sys_get_trip_temp(struct thermal_zone_device *tzone,
int trip, unsigned long *temp)
{
struct acpi_device *device = tzone->devdata;
struct int3403_sensor *obj = acpi_driver_data(device);
struct int3403_priv *priv = tzone->devdata;
struct int3403_sensor *obj = priv->priv;
if (priv->type != INT3403_TYPE_SENSOR || !obj)
return -EINVAL;
if (trip == obj->crit_trip_id)
*temp = obj->crit_temp;
......@@ -91,7 +114,7 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzone,
/*
* get_trip_temp is a mandatory callback but
* PATx method doesn't return any value, so return
* cached value, which was last set from user space.
* cached value, which was last set from user space
*/
*temp = obj->thresholds[trip];
}
......@@ -102,8 +125,8 @@ static int sys_get_trip_temp(struct thermal_zone_device *tzone,
static int sys_get_trip_type(struct thermal_zone_device *thermal,
int trip, enum thermal_trip_type *type)
{
struct acpi_device *device = thermal->devdata;
struct int3403_sensor *obj = acpi_driver_data(device);
struct int3403_priv *priv = thermal->devdata;
struct int3403_sensor *obj = priv->priv;
/* Mandatory callback, may not mean much here */
if (trip == obj->crit_trip_id)
......@@ -117,11 +140,12 @@ static int sys_get_trip_type(struct thermal_zone_device *thermal,
int sys_set_trip_temp(struct thermal_zone_device *tzone, int trip,
unsigned long temp)
{
struct acpi_device *device = tzone->devdata;
struct int3403_priv *priv = tzone->devdata;
struct acpi_device *device = priv->adev;
struct int3403_sensor *obj = priv->priv;
acpi_status status;
char name[10];
int ret = 0;
struct int3403_sensor *obj = acpi_driver_data(device);
snprintf(name, sizeof(name), "PAT%d", trip);
if (acpi_has_method(device->handle, name)) {
......@@ -148,15 +172,22 @@ static struct thermal_zone_device_ops tzone_ops = {
.get_trip_hyst = sys_get_trip_hyst,
};
static void acpi_thermal_notify(struct acpi_device *device, u32 event)
static struct thermal_zone_params int3403_thermal_params = {
.governor_name = "user_space",
.no_hwmon = true,
};
static void int3403_notify(acpi_handle handle,
u32 event, void *data)
{
struct int3403_priv *priv = data;
struct int3403_sensor *obj;
if (!device)
if (!priv)
return;
obj = acpi_driver_data(device);
if (!obj)
obj = priv->priv;
if (priv->type != INT3403_TYPE_SENSOR || !obj)
return;
switch (event) {
......@@ -166,7 +197,7 @@ static void acpi_thermal_notify(struct acpi_device *device, u32 event)
thermal_zone_device_update(obj->tzone);
break;
default:
dev_err(&device->dev, "Unsupported event [0x%x]\n", event);
dev_err(&priv->pdev->dev, "Unsupported event [0x%x]\n", event);
break;
}
}
......@@ -199,97 +230,247 @@ static int sys_get_trip_psv(struct acpi_device *device, unsigned long *temp)
return 0;
}
static int acpi_int3403_add(struct acpi_device *device)
static int int3403_sensor_add(struct int3403_priv *priv)
{
int result = 0;
unsigned long long ptyp;
acpi_status status;
struct int3403_sensor *obj;
unsigned long long trip_cnt;
int trip_mask = 0;
if (!device)
return -EINVAL;
status = acpi_evaluate_integer(device->handle, "PTYP", NULL, &ptyp);
if (ACPI_FAILURE(status))
return -EINVAL;
if (ptyp != INT3403_TYPE_SENSOR)
return -EINVAL;
obj = devm_kzalloc(&device->dev, sizeof(*obj), GFP_KERNEL);
obj = devm_kzalloc(&priv->pdev->dev, sizeof(*obj), GFP_KERNEL);
if (!obj)
return -ENOMEM;
device->driver_data = obj;
priv->priv = obj;
status = acpi_evaluate_integer(device->handle, "PATC", NULL,
status = acpi_evaluate_integer(priv->adev->handle, "PATC", NULL,
&trip_cnt);
if (ACPI_FAILURE(status))
trip_cnt = 0;
if (trip_cnt) {
/* We have to cache, thresholds can't be readback */
obj->thresholds = devm_kzalloc(&device->dev,
obj->thresholds = devm_kzalloc(&priv->pdev->dev,
sizeof(*obj->thresholds) * trip_cnt,
GFP_KERNEL);
if (!obj->thresholds)
return -ENOMEM;
if (!obj->thresholds) {
result = -ENOMEM;
goto err_free_obj;
}
trip_mask = BIT(trip_cnt) - 1;
}
obj->psv_trip_id = -1;
if (!sys_get_trip_psv(device, &obj->psv_temp))
if (!sys_get_trip_psv(priv->adev, &obj->psv_temp))
obj->psv_trip_id = trip_cnt++;
obj->crit_trip_id = -1;
if (!sys_get_trip_crt(device, &obj->crit_temp))
if (!sys_get_trip_crt(priv->adev, &obj->crit_temp))
obj->crit_trip_id = trip_cnt++;
obj->tzone = thermal_zone_device_register(acpi_device_bid(device),
trip_cnt, trip_mask, device, &tzone_ops,
NULL, 0, 0);
obj->tzone = thermal_zone_device_register(acpi_device_bid(priv->adev),
trip_cnt, trip_mask, priv, &tzone_ops,
&int3403_thermal_params, 0, 0);
if (IS_ERR(obj->tzone)) {
result = PTR_ERR(obj->tzone);
return result;
obj->tzone = NULL;
goto err_free_obj;
}
strcpy(acpi_device_name(device), "INT3403");
strcpy(acpi_device_class(device), ACPI_INT3403_CLASS);
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:
if (obj->tzone)
thermal_zone_device_unregister(obj->tzone);
return result;
}
static int acpi_int3403_remove(struct acpi_device *device)
static int int3403_sensor_remove(struct int3403_priv *priv)
{
struct int3403_sensor *obj;
struct int3403_sensor *obj = priv->priv;
obj = acpi_driver_data(device);
thermal_zone_device_unregister(obj->tzone);
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)) {
printk(KERN_WARNING "Invalid PPSS data\n");
return -EFAULT;
}
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);
priv->priv = obj;
/* 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;
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, "PTYP",
NULL, &priv->type);
if (ACPI_FAILURE(status)) {
result = -EINVAL;
goto err;
}
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;
}
ACPI_MODULE_NAME("int3403");
static const struct acpi_device_id int3403_device_ids[] = {
{"INT3403", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, int3403_device_ids);
static struct acpi_driver acpi_int3403_driver = {
.name = "INT3403",
.class = ACPI_INT3403_CLASS,
.ids = int3403_device_ids,
.ops = {
.add = acpi_int3403_add,
.remove = acpi_int3403_remove,
.notify = acpi_thermal_notify,
},
static struct platform_driver int3403_driver = {
.probe = int3403_add,
.remove = int3403_remove,
.driver = {
.name = "int3403 thermal",
.owner = THIS_MODULE,
.acpi_match_table = int3403_device_ids,
},
};
module_acpi_driver(acpi_int3403_driver);
module_platform_driver(int3403_driver);
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_LICENSE("GPL v2");
......
......@@ -435,6 +435,7 @@ int acpi_device_set_power(struct acpi_device *device, int state);
int acpi_bus_init_power(struct acpi_device *device);
int acpi_device_fix_up_power(struct acpi_device *device);
int acpi_bus_update_power(acpi_handle handle, int *state_p);
int acpi_device_update_power(struct acpi_device *device, int *state_p);
bool acpi_bus_power_manageable(acpi_handle handle);
#ifdef CONFIG_PM
......
......@@ -432,6 +432,7 @@ static inline bool acpi_driver_match_device(struct device *dev,
int acpi_device_uevent_modalias(struct device *, struct kobj_uevent_env *);
int acpi_device_modalias(struct device *, char *, int);
struct platform_device *acpi_create_platform_device(struct acpi_device *);
#define ACPI_PTR(_ptr) (_ptr)
#else /* !CONFIG_ACPI */
......
......@@ -44,6 +44,10 @@
#define KELVIN_TO_CELSIUS(t) (long)(((long)t-2732 >= 0) ? \
((long)t-2732+5)/10 : ((long)t-2732-5)/10)
#define CELSIUS_TO_KELVIN(t) ((t)*10+2732)
#define DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(t, off) (((t) - (off)) * 100)
#define DECI_KELVIN_TO_MILLICELSIUS(t) DECI_KELVIN_TO_MILLICELSIUS_WITH_OFFSET(t, 2732)
#define MILLICELSIUS_TO_DECI_KELVIN_WITH_OFFSET(t, off) (((t) / 100) + (off))
#define MILLICELSIUS_TO_DECI_KELVIN(t) MILLICELSIUS_TO_DECI_KELVIN_WITH_OFFSET(t, 2732)
/* Adding event notification support elements */
#define THERMAL_GENL_FAMILY_NAME "thermal_event"
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment