Merge branch 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux

Pull thermal management updates from Zhang Rui:

 - Add locking for cooling device sysfs attribute in case the cooling
   device state is changed by userspace and thermal framework
   simultaneously. (Thara Gopinath)

 - Fix a problem that passive cooling is reset improperly after system
   suspend/resume. (Wei Wang)

 - Cleanup the driver/thermal/ directory by moving intel and qcom
   platform specific drivers to platform specific sub-directories. (Amit
   Kucheria)

 - Some trivial cleanups. (Lukasz Luba, Wolfram Sang)

* 'next' of git://git.kernel.org/pub/scm/linux/kernel/git/rzhang/linux:
  thermal/intel: fixup for Kconfig string parsing tightening up
  drivers: thermal: Move QCOM_SPMI_TEMP_ALARM into the qcom subdir
  drivers: thermal: Move various drivers for intel platforms into a subdir
  thermal: Fix locking in cooling device sysfs update cur_state
  Thermal: do not clear passive state during system sleep
  thermal: zx2967_thermal: simplify getting .driver_data
  thermal: st: st_thermal: simplify getting .driver_data
  thermal: spear_thermal: simplify getting .driver_data
  thermal: rockchip_thermal: simplify getting .driver_data
  thermal: int340x_thermal: int3400_thermal: simplify getting .driver_data
  thermal: remove unused function parameter
This commit is contained in:
Linus Torvalds
2019-01-05 16:07:28 -08:00
32 changed files with 138 additions and 135 deletions

View File

@@ -0,0 +1,77 @@
config INTEL_POWERCLAMP
tristate "Intel PowerClamp idle injection driver"
depends on THERMAL
depends on X86
depends on CPU_SUP_INTEL
help
Enable this to enable Intel PowerClamp idle injection driver. This
enforce idle time which results in more package C-state residency. The
user interface is exposed via generic thermal framework.
config X86_PKG_TEMP_THERMAL
tristate "X86 package temperature thermal driver"
depends on X86_THERMAL_VECTOR
select THERMAL_GOV_USER_SPACE
select THERMAL_WRITABLE_TRIPS
default m
help
Enable this to register CPU digital sensor for package temperature as
thermal zone. Each package will have its own thermal zone. There are
two trip points which can be set by user to get notifications via thermal
notification methods.
config INTEL_SOC_DTS_IOSF_CORE
tristate
depends on X86 && PCI
select IOSF_MBI
help
This is becoming a common feature for Intel SoCs to expose the additional
digital temperature sensors (DTSs) using side band interface (IOSF). This
implements the common set of helper functions to register, get temperature
and get/set thresholds on DTSs.
config INTEL_SOC_DTS_THERMAL
tristate "Intel SoCs DTS thermal driver"
depends on X86 && PCI && ACPI
select INTEL_SOC_DTS_IOSF_CORE
select THERMAL_WRITABLE_TRIPS
help
Enable this to register Intel SoCs (e.g. Bay Trail) platform digital
temperature sensor (DTS). These SoCs have two additional DTSs in
addition to DTSs on CPU cores. Each DTS will be registered as a
thermal zone. There are two trip points. One of the trip point can
be set by user mode programs to get notifications via Linux thermal
notification methods.The other trip is a critical trip point, which
was set by the driver based on the TJ MAX temperature.
config INTEL_QUARK_DTS_THERMAL
tristate "Intel Quark DTS thermal driver"
depends on X86_INTEL_QUARK
help
Enable this to register Intel Quark SoC (e.g. X1000) platform digital
temperature sensor (DTS). For X1000 SoC, it has one on-die DTS.
The DTS will be registered as a thermal zone. There are two trip points:
hot & critical. The critical trip point default value is set by
underlying BIOS/Firmware.
menu "ACPI INT340X thermal drivers"
source "drivers/thermal/intel/int340x_thermal/Kconfig"
endmenu
config INTEL_BXT_PMIC_THERMAL
tristate "Intel Broxton PMIC thermal driver"
depends on X86 && INTEL_SOC_PMIC_BXTWC && REGMAP
help
Select this driver for Intel Broxton PMIC with ADC channels monitoring
system temperature measurements and alerts.
This driver is used for monitoring the ADC channels of PMIC and handles
the alert trip point interrupts and notifies the thermal framework with
the trip point and temperature details of the zone.
config INTEL_PCH_THERMAL
tristate "Intel PCH Thermal Reporting Driver"
depends on X86 && PCI
help
Enable this to support thermal reporting on certain intel PCHs.
Thermal reporting device will provide temperature reading,
programmable trip points and other information.

View File

@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for various Intel thermal drivers.
obj-$(CONFIG_INTEL_POWERCLAMP) += intel_powerclamp.o
obj-$(CONFIG_X86_PKG_TEMP_THERMAL) += x86_pkg_temp_thermal.o
obj-$(CONFIG_INTEL_SOC_DTS_IOSF_CORE) += intel_soc_dts_iosf.o
obj-$(CONFIG_INTEL_SOC_DTS_THERMAL) += intel_soc_dts_thermal.o
obj-$(CONFIG_INTEL_QUARK_DTS_THERMAL) += intel_quark_dts_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal/
obj-$(CONFIG_INTEL_BXT_PMIC_THERMAL) += intel_bxt_pmic_thermal.o
obj-$(CONFIG_INTEL_PCH_THERMAL) += intel_pch_thermal.o

View File

@@ -0,0 +1,42 @@
#
# ACPI INT340x thermal drivers configuration
#
config INT340X_THERMAL
tristate "ACPI INT340X thermal drivers"
depends on X86 && ACPI
select THERMAL_GOV_USER_SPACE
select ACPI_THERMAL_REL
select ACPI_FAN
select INTEL_SOC_DTS_IOSF_CORE
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
if INT340X_THERMAL
config INT3406_THERMAL
tristate "ACPI INT3406 display thermal driver"
depends on ACPI_VIDEO
help
The display thermal device represents the LED/LCD display panel
that may or may not include touch support. The main function of
the display thermal device is to allow control of the display
brightness in order to address a thermal condition or to reduce
power consumed by display device.
endif

View File

@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_INT340X_THERMAL) += int3400_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int340x_thermal_zone.o
obj-$(CONFIG_INT340X_THERMAL) += int3402_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += int3403_thermal.o
obj-$(CONFIG_INT340X_THERMAL) += processor_thermal_device.o
obj-$(CONFIG_INT3406_THERMAL) += int3406_thermal.o
obj-$(CONFIG_ACPI_THERMAL_REL) += acpi_thermal_rel.o

View File

@@ -0,0 +1,394 @@
/* 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
* @trt_count: the number of valid entries resulted from parsing _TRT
* @trtp: pointer to pointer of array of _TRT 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 -ENODEV;
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 = kcalloc(*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)
pr_warn("Failed to get source ACPI device\n");
result = acpi_bus_get_device(trt->target, &adev);
if (result)
pr_warn("Failed to get target ACPI device\n");
}
result = 0;
*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 -ENODEV;
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 = kcalloc(*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)
pr_warn("Failed to get source ACPI device\n");
}
if (art->target) {
result = acpi_bus_get_device(art->target, &adev);
if (result)
pr_warn("Failed to get target 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 to get device name from acpi 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;
int count = 0;
char __user *arg = (void __user *)__arg;
struct trt *trts = NULL;
struct art *arts = NULL;
switch (cmd) {
case ACPI_THERMAL_GET_TRT_COUNT:
ret = acpi_parse_trt(acpi_thermal_rel_handle, &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, &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, &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, &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");

View File

@@ -0,0 +1,85 @@
/* SPDX-License-Identifier: GPL-2.0 */
#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 reserved1;
u64 reserved2;
u64 reserved3;
u64 reserved4;
} __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 */

View File

@@ -0,0 +1,382 @@
/*
* 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"
#define INT3400_THERMAL_TABLE_CHANGED 0x83
enum int3400_thermal_uuid {
INT3400_THERMAL_PASSIVE_1,
INT3400_THERMAL_ACTIVE,
INT3400_THERMAL_CRITICAL,
INT3400_THERMAL_MAXIMUM_UUID,
};
static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
"42A441D6-AE6A-462b-A84B-4A8CE79027D3",
"3A95C389-E4B8-4629-A526-C52C88626BAE",
"97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
};
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;
int current_uuid_index;
};
static ssize_t available_uuids_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
int i;
int length = 0;
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
if (priv->uuid_bitmap & (1 << i))
if (PAGE_SIZE - length > 0)
length += snprintf(&buf[length],
PAGE_SIZE - length,
"%s\n",
int3400_thermal_uuids[i]);
}
return length;
}
static ssize_t current_uuid_show(struct device *dev,
struct device_attribute *devattr, char *buf)
{
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
if (priv->uuid_bitmap & (1 << priv->current_uuid_index))
return sprintf(buf, "%s\n",
int3400_thermal_uuids[priv->current_uuid_index]);
else
return sprintf(buf, "INVALID\n");
}
static ssize_t current_uuid_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
int i;
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
if ((priv->uuid_bitmap & (1 << i)) &&
!(strncmp(buf, int3400_thermal_uuids[i],
sizeof(int3400_thermal_uuids[i]) - 1))) {
priv->current_uuid_index = i;
return count;
}
}
return -EINVAL;
}
static DEVICE_ATTR_RW(current_uuid);
static DEVICE_ATTR_RO(available_uuids);
static struct attribute *uuid_attrs[] = {
&dev_attr_available_uuids.attr,
&dev_attr_current_uuid.attr,
NULL
};
static const struct attribute_group uuid_attribute_group = {
.attrs = uuid_attrs,
.name = "uuids"
};
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++) {
guid_t guid;
guid_parse(int3400_thermal_uuids[j], &guid);
if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) {
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 void int3400_notify(acpi_handle handle,
u32 event,
void *data)
{
struct int3400_thermal_priv *priv = data;
char *thermal_prop[5];
if (!priv)
return;
switch (event) {
case INT3400_THERMAL_TABLE_CHANGED:
thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s",
priv->thermal->type);
thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d",
priv->thermal->temperature);
thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");
thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d",
THERMAL_TABLE_CHANGED);
thermal_prop[4] = NULL;
kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE,
thermal_prop);
break;
default:
/* Ignore unknown notification codes sent to INT3400 device */
break;
}
}
static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
int *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;
result = int3400_thermal_run_osc(priv->adev->handle,
priv->current_uuid_index,
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)
dev_dbg(&pdev->dev, "_ART table parsing error\n");
result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,
&priv->trts, true);
if (result)
dev_dbg(&pdev->dev, "_TRT table parsing error\n");
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_art_trt;
}
priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
priv->adev->handle);
result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);
if (result)
goto free_rel_misc;
result = acpi_install_notify_handler(
priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
(void *)priv);
if (result)
goto free_sysfs;
return 0;
free_sysfs:
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
free_rel_misc:
if (!priv->rel_misc_dev_res)
acpi_thermal_rel_misc_device_remove(priv->adev->handle);
thermal_zone_device_unregister(priv->thermal);
free_art_trt:
kfree(priv->trts);
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);
acpi_remove_notify_handler(
priv->adev->handle, ACPI_DEVICE_NOTIFY,
int3400_notify);
if (!priv->rel_misc_dev_res)
acpi_thermal_rel_misc_device_remove(priv->adev->handle);
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
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",
.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");

View File

@@ -0,0 +1,108 @@
/*
* 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>
#include "int340x_thermal_zone.h"
#define INT3402_PERF_CHANGED_EVENT 0x80
#define INT3402_THERMAL_EVENT 0x90
struct int3402_thermal_data {
acpi_handle *handle;
struct int34x_thermal_zone *int340x_zone;
};
static void int3402_notify(acpi_handle handle, u32 event, void *data)
{
struct int3402_thermal_data *priv = data;
if (!priv)
return;
switch (event) {
case INT3402_PERF_CHANGED_EVENT:
break;
case INT3402_THERMAL_EVENT:
int340x_thermal_zone_device_update(priv->int340x_zone,
THERMAL_TRIP_VIOLATED);
break;
default:
break;
}
}
static int int3402_thermal_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
struct int3402_thermal_data *d;
int ret;
if (!acpi_has_method(adev->handle, "_TMP"))
return -ENODEV;
d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
d->int340x_zone = int340x_thermal_zone_add(adev, NULL);
if (IS_ERR(d->int340x_zone))
return PTR_ERR(d->int340x_zone);
ret = acpi_install_notify_handler(adev->handle,
ACPI_DEVICE_NOTIFY,
int3402_notify,
d);
if (ret) {
int340x_thermal_zone_remove(d->int340x_zone);
return ret;
}
d->handle = adev->handle;
platform_set_drvdata(pdev, d);
return 0;
}
static int int3402_thermal_remove(struct platform_device *pdev)
{
struct int3402_thermal_data *d = platform_get_drvdata(pdev);
acpi_remove_notify_handler(d->handle,
ACPI_DEVICE_NOTIFY, int3402_notify);
int340x_thermal_zone_remove(d->int340x_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",
.acpi_match_table = int3402_thermal_match,
},
};
module_platform_driver(int3402_thermal_driver);
MODULE_DESCRIPTION("INT3402 Thermal driver");
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,311 @@
/*
* ACPI INT3403 thermal driver
* Copyright (c) 2013, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*/
#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_err(&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)) {
printk(KERN_WARNING "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;
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)) {
unsigned long long tmp;
status = acpi_evaluate_integer(priv->adev->handle, "_TMP",
NULL, &tmp);
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},
{"", 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 <srinivas.pandruvada@linux.intel.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("ACPI INT3403 thermal driver");

View File

@@ -0,0 +1,213 @@
/*
* INT3406 thermal driver for display participant device
*
* Copyright (C) 2016, 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/backlight.h>
#include <linux/thermal.h>
#include <acpi/video.h>
#define INT3406_BRIGHTNESS_LIMITS_CHANGED 0x80
struct int3406_thermal_data {
int upper_limit;
int lower_limit;
acpi_handle handle;
struct acpi_video_device_brightness *br;
struct backlight_device *raw_bd;
struct thermal_cooling_device *cooling_dev;
};
/*
* According to the ACPI spec,
* "Each brightness level is represented by a number between 0 and 100,
* and can be thought of as a percentage. For example, 50 can be 50%
* power consumption or 50% brightness, as defined by the OEM."
*
* As int3406 device uses this value to communicate with the native
* graphics driver, we make the assumption that it represents
* the percentage of brightness only
*/
#define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100)
#define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness)
static int
int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev,
unsigned long *state)
{
struct int3406_thermal_data *d = cooling_dev->devdata;
*state = d->upper_limit - d->lower_limit;
return 0;
}
static int
int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev,
unsigned long state)
{
struct int3406_thermal_data *d = cooling_dev->devdata;
int acpi_level, raw_level;
if (state > d->upper_limit - d->lower_limit)
return -EINVAL;
acpi_level = d->br->levels[d->upper_limit - state];
raw_level = ACPI_TO_RAW(acpi_level, d);
return backlight_device_set_brightness(d->raw_bd, raw_level);
}
static int
int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev,
unsigned long *state)
{
struct int3406_thermal_data *d = cooling_dev->devdata;
int acpi_level;
int index;
acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d);
/*
* There is no 1:1 mapping between the firmware interface level
* with the raw interface level, we will have to find one that is
* right above it.
*/
for (index = d->lower_limit; index < d->upper_limit; index++) {
if (acpi_level <= d->br->levels[index])
break;
}
*state = d->upper_limit - index;
return 0;
}
static const struct thermal_cooling_device_ops video_cooling_ops = {
.get_max_state = int3406_thermal_get_max_state,
.get_cur_state = int3406_thermal_get_cur_state,
.set_cur_state = int3406_thermal_set_cur_state,
};
static int int3406_thermal_get_index(int *array, int nr, int value)
{
int i;
for (i = 2; i < nr; i++) {
if (array[i] == value)
break;
}
return i == nr ? -ENOENT : i;
}
static void int3406_thermal_get_limit(struct int3406_thermal_data *d)
{
acpi_status status;
unsigned long long lower_limit, upper_limit;
status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit);
if (ACPI_SUCCESS(status))
d->lower_limit = int3406_thermal_get_index(d->br->levels,
d->br->count, lower_limit);
status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit);
if (ACPI_SUCCESS(status))
d->upper_limit = int3406_thermal_get_index(d->br->levels,
d->br->count, upper_limit);
/* lower_limit and upper_limit should be always set */
d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2;
d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1;
}
static void int3406_notify(acpi_handle handle, u32 event, void *data)
{
if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED)
int3406_thermal_get_limit(data);
}
static int int3406_thermal_probe(struct platform_device *pdev)
{
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
struct int3406_thermal_data *d;
struct backlight_device *bd;
int ret;
if (!ACPI_HANDLE(&pdev->dev))
return -ENODEV;
d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
if (!d)
return -ENOMEM;
d->handle = ACPI_HANDLE(&pdev->dev);
bd = backlight_device_get_by_type(BACKLIGHT_RAW);
if (!bd)
return -ENODEV;
d->raw_bd = bd;
ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br, NULL);
if (ret)
return ret;
int3406_thermal_get_limit(d);
d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev),
d, &video_cooling_ops);
if (IS_ERR(d->cooling_dev))
goto err;
ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
int3406_notify, d);
if (ret)
goto err_cdev;
platform_set_drvdata(pdev, d);
return 0;
err_cdev:
thermal_cooling_device_unregister(d->cooling_dev);
err:
kfree(d->br);
return -ENODEV;
}
static int int3406_thermal_remove(struct platform_device *pdev)
{
struct int3406_thermal_data *d = platform_get_drvdata(pdev);
thermal_cooling_device_unregister(d->cooling_dev);
kfree(d->br);
return 0;
}
static const struct acpi_device_id int3406_thermal_match[] = {
{"INT3406", 0},
{}
};
MODULE_DEVICE_TABLE(acpi, int3406_thermal_match);
static struct platform_driver int3406_thermal_driver = {
.probe = int3406_thermal_probe,
.remove = int3406_thermal_remove,
.driver = {
.name = "int3406 thermal",
.acpi_match_table = int3406_thermal_match,
},
};
module_platform_driver(int3406_thermal_driver);
MODULE_DESCRIPTION("INT3406 Thermal driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,295 @@
/*
* int340x_thermal_zone.c
* Copyright (c) 2015, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/acpi.h>
#include <linux/thermal.h>
#include "int340x_thermal_zone.h"
static int int340x_thermal_get_zone_temp(struct thermal_zone_device *zone,
int *temp)
{
struct int34x_thermal_zone *d = zone->devdata;
unsigned long long tmp;
acpi_status status;
if (d->override_ops && d->override_ops->get_temp)
return d->override_ops->get_temp(zone, temp);
status = acpi_evaluate_integer(d->adev->handle, "_TMP", NULL, &tmp);
if (ACPI_FAILURE(status))
return -EIO;
if (d->lpat_table) {
int conv_temp;
conv_temp = acpi_lpat_raw_to_temp(d->lpat_table, (int)tmp);
if (conv_temp < 0)
return conv_temp;
*temp = (unsigned long)conv_temp * 10;
} else
/* _TMP returns the temperature in tenths of degrees Kelvin */
*temp = DECI_KELVIN_TO_MILLICELSIUS(tmp);
return 0;
}
static int int340x_thermal_get_trip_temp(struct thermal_zone_device *zone,
int trip, int *temp)
{
struct int34x_thermal_zone *d = zone->devdata;
int i;
if (d->override_ops && d->override_ops->get_trip_temp)
return d->override_ops->get_trip_temp(zone, trip, temp);
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 < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
if (d->act_trips[i].valid &&
d->act_trips[i].id == trip) {
*temp = d->act_trips[i].temp;
break;
}
}
if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT)
return -EINVAL;
}
return 0;
}
static int int340x_thermal_get_trip_type(struct thermal_zone_device *zone,
int trip,
enum thermal_trip_type *type)
{
struct int34x_thermal_zone *d = zone->devdata;
int i;
if (d->override_ops && d->override_ops->get_trip_type)
return d->override_ops->get_trip_type(zone, trip, type);
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 < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
if (d->act_trips[i].valid &&
d->act_trips[i].id == trip) {
*type = THERMAL_TRIP_ACTIVE;
break;
}
}
if (i == INT340X_THERMAL_MAX_ACT_TRIP_COUNT)
return -EINVAL;
}
return 0;
}
static int int340x_thermal_set_trip_temp(struct thermal_zone_device *zone,
int trip, int temp)
{
struct int34x_thermal_zone *d = zone->devdata;
acpi_status status;
char name[10];
if (d->override_ops && d->override_ops->set_trip_temp)
return d->override_ops->set_trip_temp(zone, trip, temp);
snprintf(name, sizeof(name), "PAT%d", trip);
status = acpi_execute_simple_method(d->adev->handle, name,
MILLICELSIUS_TO_DECI_KELVIN(temp));
if (ACPI_FAILURE(status))
return -EIO;
d->aux_trips[trip] = temp;
return 0;
}
static int int340x_thermal_get_trip_hyst(struct thermal_zone_device *zone,
int trip, int *temp)
{
struct int34x_thermal_zone *d = zone->devdata;
acpi_status status;
unsigned long long hyst;
if (d->override_ops && d->override_ops->get_trip_hyst)
return d->override_ops->get_trip_hyst(zone, trip, temp);
status = acpi_evaluate_integer(d->adev->handle, "GTSH", NULL, &hyst);
if (ACPI_FAILURE(status))
*temp = 0;
else
*temp = hyst * 100;
return 0;
}
static struct thermal_zone_device_ops int340x_thermal_zone_ops = {
.get_temp = int340x_thermal_get_zone_temp,
.get_trip_temp = int340x_thermal_get_trip_temp,
.get_trip_type = int340x_thermal_get_trip_type,
.set_trip_temp = int340x_thermal_set_trip_temp,
.get_trip_hyst = int340x_thermal_get_trip_hyst,
};
static int int340x_thermal_get_trip_config(acpi_handle handle, char *name,
int *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;
}
int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone)
{
int trip_cnt = int34x_zone->aux_trip_nr;
int i;
int34x_zone->crt_trip_id = -1;
if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_CRT",
&int34x_zone->crt_temp))
int34x_zone->crt_trip_id = trip_cnt++;
int34x_zone->hot_trip_id = -1;
if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_HOT",
&int34x_zone->hot_temp))
int34x_zone->hot_trip_id = trip_cnt++;
int34x_zone->psv_trip_id = -1;
if (!int340x_thermal_get_trip_config(int34x_zone->adev->handle, "_PSV",
&int34x_zone->psv_temp))
int34x_zone->psv_trip_id = trip_cnt++;
for (i = 0; i < INT340X_THERMAL_MAX_ACT_TRIP_COUNT; i++) {
char name[5] = { '_', 'A', 'C', '0' + i, '\0' };
if (int340x_thermal_get_trip_config(int34x_zone->adev->handle,
name,
&int34x_zone->act_trips[i].temp))
break;
int34x_zone->act_trips[i].id = trip_cnt++;
int34x_zone->act_trips[i].valid = true;
}
return trip_cnt;
}
EXPORT_SYMBOL_GPL(int340x_thermal_read_trips);
static struct thermal_zone_params int340x_thermal_params = {
.governor_name = "user_space",
.no_hwmon = true,
};
struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *adev,
struct thermal_zone_device_ops *override_ops)
{
struct int34x_thermal_zone *int34x_thermal_zone;
acpi_status status;
unsigned long long trip_cnt;
int trip_mask = 0;
int ret;
int34x_thermal_zone = kzalloc(sizeof(*int34x_thermal_zone),
GFP_KERNEL);
if (!int34x_thermal_zone)
return ERR_PTR(-ENOMEM);
int34x_thermal_zone->adev = adev;
int34x_thermal_zone->override_ops = override_ops;
status = acpi_evaluate_integer(adev->handle, "PATC", NULL, &trip_cnt);
if (ACPI_FAILURE(status))
trip_cnt = 0;
else {
int34x_thermal_zone->aux_trips =
kcalloc(trip_cnt,
sizeof(*int34x_thermal_zone->aux_trips),
GFP_KERNEL);
if (!int34x_thermal_zone->aux_trips) {
ret = -ENOMEM;
goto err_trip_alloc;
}
trip_mask = BIT(trip_cnt) - 1;
int34x_thermal_zone->aux_trip_nr = trip_cnt;
}
trip_cnt = int340x_thermal_read_trips(int34x_thermal_zone);
int34x_thermal_zone->lpat_table = acpi_lpat_get_conversion_table(
adev->handle);
int34x_thermal_zone->zone = thermal_zone_device_register(
acpi_device_bid(adev),
trip_cnt,
trip_mask, int34x_thermal_zone,
&int340x_thermal_zone_ops,
&int340x_thermal_params,
0, 0);
if (IS_ERR(int34x_thermal_zone->zone)) {
ret = PTR_ERR(int34x_thermal_zone->zone);
goto err_thermal_zone;
}
return int34x_thermal_zone;
err_thermal_zone:
acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
kfree(int34x_thermal_zone->aux_trips);
err_trip_alloc:
kfree(int34x_thermal_zone);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(int340x_thermal_zone_add);
void int340x_thermal_zone_remove(struct int34x_thermal_zone
*int34x_thermal_zone)
{
thermal_zone_device_unregister(int34x_thermal_zone->zone);
acpi_lpat_free_conversion_table(int34x_thermal_zone->lpat_table);
kfree(int34x_thermal_zone->aux_trips);
kfree(int34x_thermal_zone);
}
EXPORT_SYMBOL_GPL(int340x_thermal_zone_remove);
MODULE_AUTHOR("Aaron Lu <aaron.lu@intel.com>");
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_DESCRIPTION("Intel INT340x common thermal zone handler");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,70 @@
/*
* int340x_thermal_zone.h
* Copyright (c) 2015, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#ifndef __INT340X_THERMAL_ZONE_H__
#define __INT340X_THERMAL_ZONE_H__
#include <acpi/acpi_lpat.h>
#define INT340X_THERMAL_MAX_ACT_TRIP_COUNT 10
struct active_trip {
int temp;
int id;
bool valid;
};
struct int34x_thermal_zone {
struct acpi_device *adev;
struct active_trip act_trips[INT340X_THERMAL_MAX_ACT_TRIP_COUNT];
unsigned long *aux_trips;
int aux_trip_nr;
int psv_temp;
int psv_trip_id;
int crt_temp;
int crt_trip_id;
int hot_temp;
int hot_trip_id;
struct thermal_zone_device *zone;
struct thermal_zone_device_ops *override_ops;
void *priv_data;
struct acpi_lpat_conversion_table *lpat_table;
};
struct int34x_thermal_zone *int340x_thermal_zone_add(struct acpi_device *,
struct thermal_zone_device_ops *override_ops);
void int340x_thermal_zone_remove(struct int34x_thermal_zone *);
int int340x_thermal_read_trips(struct int34x_thermal_zone *int34x_zone);
static inline void int340x_thermal_zone_set_priv_data(
struct int34x_thermal_zone *tzone, void *priv_data)
{
tzone->priv_data = priv_data;
}
static inline void *int340x_thermal_zone_get_priv_data(
struct int34x_thermal_zone *tzone)
{
return tzone->priv_data;
}
static inline void int340x_thermal_zone_device_update(
struct int34x_thermal_zone *tzone,
enum thermal_notify_event event)
{
thermal_zone_device_update(tzone->zone, event);
}
#endif

View File

@@ -0,0 +1,525 @@
/*
* processor_thermal_device.c
* Copyright (c) 2014, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
#include <linux/thermal.h>
#include "int340x_thermal_zone.h"
#include "../intel_soc_dts_iosf.h"
/* Broadwell-U/HSB thermal reporting device */
#define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603
#define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03
/* Skylake thermal reporting device */
#define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903
/* CannonLake thermal reporting device */
#define PCI_DEVICE_ID_PROC_CNL_THERMAL 0x5a03
#define PCI_DEVICE_ID_PROC_CFL_THERMAL 0x3E83
/* Braswell thermal reporting device */
#define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC
/* Broxton thermal reporting device */
#define PCI_DEVICE_ID_PROC_BXT0_THERMAL 0x0A8C
#define PCI_DEVICE_ID_PROC_BXT1_THERMAL 0x1A8C
#define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C
#define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C
/* GeminiLake thermal reporting device */
#define PCI_DEVICE_ID_PROC_GLK_THERMAL 0x318C
struct power_config {
u32 index;
u32 min_uw;
u32 max_uw;
u32 tmin_us;
u32 tmax_us;
u32 step_uw;
};
struct proc_thermal_device {
struct device *dev;
struct acpi_device *adev;
struct power_config power_limits[2];
struct int34x_thermal_zone *int340x_zone;
struct intel_soc_dts_sensors *soc_dts;
};
enum proc_thermal_emum_mode_type {
PROC_THERMAL_NONE,
PROC_THERMAL_PCI,
PROC_THERMAL_PLATFORM_DEV
};
/*
* We can have only one type of enumeration, PCI or Platform,
* not both. So we don't need instance specific data.
*/
static enum proc_thermal_emum_mode_type proc_thermal_emum_mode =
PROC_THERMAL_NONE;
#define POWER_LIMIT_SHOW(index, suffix) \
static ssize_t power_limit_##index##_##suffix##_show(struct device *dev, \
struct device_attribute *attr, \
char *buf) \
{ \
struct pci_dev *pci_dev; \
struct platform_device *pdev; \
struct proc_thermal_device *proc_dev; \
\
if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) { \
pdev = to_platform_device(dev); \
proc_dev = platform_get_drvdata(pdev); \
} else { \
pci_dev = to_pci_dev(dev); \
proc_dev = pci_get_drvdata(pci_dev); \
} \
return sprintf(buf, "%lu\n",\
(unsigned long)proc_dev->power_limits[index].suffix * 1000); \
}
POWER_LIMIT_SHOW(0, min_uw)
POWER_LIMIT_SHOW(0, max_uw)
POWER_LIMIT_SHOW(0, step_uw)
POWER_LIMIT_SHOW(0, tmin_us)
POWER_LIMIT_SHOW(0, tmax_us)
POWER_LIMIT_SHOW(1, min_uw)
POWER_LIMIT_SHOW(1, max_uw)
POWER_LIMIT_SHOW(1, step_uw)
POWER_LIMIT_SHOW(1, tmin_us)
POWER_LIMIT_SHOW(1, tmax_us)
static DEVICE_ATTR_RO(power_limit_0_min_uw);
static DEVICE_ATTR_RO(power_limit_0_max_uw);
static DEVICE_ATTR_RO(power_limit_0_step_uw);
static DEVICE_ATTR_RO(power_limit_0_tmin_us);
static DEVICE_ATTR_RO(power_limit_0_tmax_us);
static DEVICE_ATTR_RO(power_limit_1_min_uw);
static DEVICE_ATTR_RO(power_limit_1_max_uw);
static DEVICE_ATTR_RO(power_limit_1_step_uw);
static DEVICE_ATTR_RO(power_limit_1_tmin_us);
static DEVICE_ATTR_RO(power_limit_1_tmax_us);
static struct attribute *power_limit_attrs[] = {
&dev_attr_power_limit_0_min_uw.attr,
&dev_attr_power_limit_1_min_uw.attr,
&dev_attr_power_limit_0_max_uw.attr,
&dev_attr_power_limit_1_max_uw.attr,
&dev_attr_power_limit_0_step_uw.attr,
&dev_attr_power_limit_1_step_uw.attr,
&dev_attr_power_limit_0_tmin_us.attr,
&dev_attr_power_limit_1_tmin_us.attr,
&dev_attr_power_limit_0_tmax_us.attr,
&dev_attr_power_limit_1_tmax_us.attr,
NULL
};
static const struct attribute_group power_limit_attribute_group = {
.attrs = power_limit_attrs,
.name = "power_limits"
};
static int stored_tjmax; /* since it is fixed, we can have local storage */
static int get_tjmax(void)
{
u32 eax, edx;
u32 val;
int err;
err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
if (err)
return err;
val = (eax >> 16) & 0xff;
if (val)
return val;
return -EINVAL;
}
static int read_temp_msr(int *temp)
{
int cpu;
u32 eax, edx;
int err;
unsigned long curr_temp_off = 0;
*temp = 0;
for_each_online_cpu(cpu) {
err = rdmsr_safe_on_cpu(cpu, MSR_IA32_THERM_STATUS, &eax,
&edx);
if (err)
goto err_ret;
else {
if (eax & 0x80000000) {
curr_temp_off = (eax >> 16) & 0x7f;
if (!*temp || curr_temp_off < *temp)
*temp = curr_temp_off;
} else {
err = -EINVAL;
goto err_ret;
}
}
}
return 0;
err_ret:
return err;
}
static int proc_thermal_get_zone_temp(struct thermal_zone_device *zone,
int *temp)
{
int ret;
ret = read_temp_msr(temp);
if (!ret)
*temp = (stored_tjmax - *temp) * 1000;
return ret;
}
static struct thermal_zone_device_ops proc_thermal_local_ops = {
.get_temp = proc_thermal_get_zone_temp,
};
static int proc_thermal_read_ppcc(struct proc_thermal_device *proc_priv)
{
int i;
acpi_status status;
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *elements, *ppcc;
union acpi_object *p;
int ret = 0;
status = acpi_evaluate_object(proc_priv->adev->handle, "PPCC",
NULL, &buf);
if (ACPI_FAILURE(status))
return -ENODEV;
p = buf.pointer;
if (!p || (p->type != ACPI_TYPE_PACKAGE)) {
dev_err(proc_priv->dev, "Invalid PPCC data\n");
ret = -EFAULT;
goto free_buffer;
}
if (!p->package.count) {
dev_err(proc_priv->dev, "Invalid PPCC package size\n");
ret = -EFAULT;
goto free_buffer;
}
for (i = 0; i < min((int)p->package.count - 1, 2); ++i) {
elements = &(p->package.elements[i+1]);
if (elements->type != ACPI_TYPE_PACKAGE ||
elements->package.count != 6) {
ret = -EFAULT;
goto free_buffer;
}
ppcc = elements->package.elements;
proc_priv->power_limits[i].index = ppcc[0].integer.value;
proc_priv->power_limits[i].min_uw = ppcc[1].integer.value;
proc_priv->power_limits[i].max_uw = ppcc[2].integer.value;
proc_priv->power_limits[i].tmin_us = ppcc[3].integer.value;
proc_priv->power_limits[i].tmax_us = ppcc[4].integer.value;
proc_priv->power_limits[i].step_uw = ppcc[5].integer.value;
}
free_buffer:
kfree(buf.pointer);
return ret;
}
#define PROC_POWER_CAPABILITY_CHANGED 0x83
static void proc_thermal_notify(acpi_handle handle, u32 event, void *data)
{
struct proc_thermal_device *proc_priv = data;
if (!proc_priv)
return;
switch (event) {
case PROC_POWER_CAPABILITY_CHANGED:
proc_thermal_read_ppcc(proc_priv);
int340x_thermal_zone_device_update(proc_priv->int340x_zone,
THERMAL_DEVICE_POWER_CAPABILITY_CHANGED);
break;
default:
dev_err(proc_priv->dev, "Unsupported event [0x%x]\n", event);
break;
}
}
static int proc_thermal_add(struct device *dev,
struct proc_thermal_device **priv)
{
struct proc_thermal_device *proc_priv;
struct acpi_device *adev;
acpi_status status;
unsigned long long tmp;
struct thermal_zone_device_ops *ops = NULL;
int ret;
adev = ACPI_COMPANION(dev);
if (!adev)
return -ENODEV;
proc_priv = devm_kzalloc(dev, sizeof(*proc_priv), GFP_KERNEL);
if (!proc_priv)
return -ENOMEM;
proc_priv->dev = dev;
proc_priv->adev = adev;
*priv = proc_priv;
ret = proc_thermal_read_ppcc(proc_priv);
if (!ret) {
ret = sysfs_create_group(&dev->kobj,
&power_limit_attribute_group);
}
if (ret)
return ret;
status = acpi_evaluate_integer(adev->handle, "_TMP", NULL, &tmp);
if (ACPI_FAILURE(status)) {
/* there is no _TMP method, add local method */
stored_tjmax = get_tjmax();
if (stored_tjmax > 0)
ops = &proc_thermal_local_ops;
}
proc_priv->int340x_zone = int340x_thermal_zone_add(adev, ops);
if (IS_ERR(proc_priv->int340x_zone)) {
ret = PTR_ERR(proc_priv->int340x_zone);
goto remove_group;
} else
ret = 0;
ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
proc_thermal_notify,
(void *)proc_priv);
if (ret)
goto remove_zone;
return 0;
remove_zone:
int340x_thermal_zone_remove(proc_priv->int340x_zone);
remove_group:
sysfs_remove_group(&proc_priv->dev->kobj,
&power_limit_attribute_group);
return ret;
}
static void proc_thermal_remove(struct proc_thermal_device *proc_priv)
{
acpi_remove_notify_handler(proc_priv->adev->handle,
ACPI_DEVICE_NOTIFY, proc_thermal_notify);
int340x_thermal_zone_remove(proc_priv->int340x_zone);
sysfs_remove_group(&proc_priv->dev->kobj,
&power_limit_attribute_group);
}
static int int3401_add(struct platform_device *pdev)
{
struct proc_thermal_device *proc_priv;
int ret;
if (proc_thermal_emum_mode == PROC_THERMAL_PCI) {
dev_err(&pdev->dev, "error: enumerated as PCI dev\n");
return -ENODEV;
}
ret = proc_thermal_add(&pdev->dev, &proc_priv);
if (ret)
return ret;
platform_set_drvdata(pdev, proc_priv);
proc_thermal_emum_mode = PROC_THERMAL_PLATFORM_DEV;
return 0;
}
static int int3401_remove(struct platform_device *pdev)
{
proc_thermal_remove(platform_get_drvdata(pdev));
return 0;
}
static irqreturn_t proc_thermal_pci_msi_irq(int irq, void *devid)
{
struct proc_thermal_device *proc_priv;
struct pci_dev *pdev = devid;
proc_priv = pci_get_drvdata(pdev);
intel_soc_dts_iosf_interrupt_handler(proc_priv->soc_dts);
return IRQ_HANDLED;
}
static int proc_thermal_pci_probe(struct pci_dev *pdev,
const struct pci_device_id *unused)
{
struct proc_thermal_device *proc_priv;
int ret;
if (proc_thermal_emum_mode == PROC_THERMAL_PLATFORM_DEV) {
dev_err(&pdev->dev, "error: enumerated as platform dev\n");
return -ENODEV;
}
ret = pci_enable_device(pdev);
if (ret < 0) {
dev_err(&pdev->dev, "error: could not enable device\n");
return ret;
}
ret = proc_thermal_add(&pdev->dev, &proc_priv);
if (ret) {
pci_disable_device(pdev);
return ret;
}
pci_set_drvdata(pdev, proc_priv);
proc_thermal_emum_mode = PROC_THERMAL_PCI;
if (pdev->device == PCI_DEVICE_ID_PROC_BSW_THERMAL) {
/*
* Enumerate additional DTS sensors available via IOSF.
* But we are not treating as a failure condition, if
* there are no aux DTSs enabled or fails. This driver
* already exposes sensors, which can be accessed via
* ACPI/MSR. So we don't want to fail for auxiliary DTSs.
*/
proc_priv->soc_dts = intel_soc_dts_iosf_init(
INTEL_SOC_DTS_INTERRUPT_MSI, 2, 0);
if (proc_priv->soc_dts && pdev->irq) {
ret = pci_enable_msi(pdev);
if (!ret) {
ret = request_threaded_irq(pdev->irq, NULL,
proc_thermal_pci_msi_irq,
IRQF_ONESHOT, "proc_thermal",
pdev);
if (ret) {
intel_soc_dts_iosf_exit(
proc_priv->soc_dts);
pci_disable_msi(pdev);
proc_priv->soc_dts = NULL;
}
}
} else
dev_err(&pdev->dev, "No auxiliary DTSs enabled\n");
}
return 0;
}
static void proc_thermal_pci_remove(struct pci_dev *pdev)
{
struct proc_thermal_device *proc_priv = pci_get_drvdata(pdev);
if (proc_priv->soc_dts) {
intel_soc_dts_iosf_exit(proc_priv->soc_dts);
if (pdev->irq) {
free_irq(pdev->irq, pdev);
pci_disable_msi(pdev);
}
}
proc_thermal_remove(proc_priv);
pci_disable_device(pdev);
}
static const struct pci_device_id proc_thermal_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BDW_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_HSB_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_SKL_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BSW_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT0_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXT1_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTX_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_BXTP_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CNL_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_CFL_THERMAL)},
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_PROC_GLK_THERMAL)},
{ 0, },
};
MODULE_DEVICE_TABLE(pci, proc_thermal_pci_ids);
static struct pci_driver proc_thermal_pci_driver = {
.name = "proc_thermal",
.probe = proc_thermal_pci_probe,
.remove = proc_thermal_pci_remove,
.id_table = proc_thermal_pci_ids,
};
static const struct acpi_device_id int3401_device_ids[] = {
{"INT3401", 0},
{"", 0},
};
MODULE_DEVICE_TABLE(acpi, int3401_device_ids);
static struct platform_driver int3401_driver = {
.probe = int3401_add,
.remove = int3401_remove,
.driver = {
.name = "int3401 thermal",
.acpi_match_table = int3401_device_ids,
},
};
static int __init proc_thermal_init(void)
{
int ret;
ret = platform_driver_register(&int3401_driver);
if (ret)
return ret;
ret = pci_register_driver(&proc_thermal_pci_driver);
return ret;
}
static void __exit proc_thermal_exit(void)
{
platform_driver_unregister(&int3401_driver);
pci_unregister_driver(&proc_thermal_pci_driver);
}
module_init(proc_thermal_init);
module_exit(proc_thermal_exit);
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_DESCRIPTION("Processor Thermal Reporting Device Driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,299 @@
/*
* Intel Broxton PMIC thermal driver
*
* Copyright (C) 2016 Intel Corporation. All rights reserved.
*
* 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/device.h>
#include <linux/thermal.h>
#include <linux/platform_device.h>
#include <linux/sched.h>
#include <linux/mfd/intel_soc_pmic.h>
#define BXTWC_THRM0IRQ 0x4E04
#define BXTWC_THRM1IRQ 0x4E05
#define BXTWC_THRM2IRQ 0x4E06
#define BXTWC_MTHRM0IRQ 0x4E12
#define BXTWC_MTHRM1IRQ 0x4E13
#define BXTWC_MTHRM2IRQ 0x4E14
#define BXTWC_STHRM0IRQ 0x4F19
#define BXTWC_STHRM1IRQ 0x4F1A
#define BXTWC_STHRM2IRQ 0x4F1B
struct trip_config_map {
u16 irq_reg;
u16 irq_en;
u16 evt_stat;
u8 irq_mask;
u8 irq_en_mask;
u8 evt_mask;
u8 trip_num;
};
struct thermal_irq_map {
char handle[20];
int num_trips;
const struct trip_config_map *trip_config;
};
struct pmic_thermal_data {
const struct thermal_irq_map *maps;
int num_maps;
};
static const struct trip_config_map bxtwc_str0_trip_config[] = {
{
.irq_reg = BXTWC_THRM0IRQ,
.irq_mask = 0x01,
.irq_en = BXTWC_MTHRM0IRQ,
.irq_en_mask = 0x01,
.evt_stat = BXTWC_STHRM0IRQ,
.evt_mask = 0x01,
.trip_num = 0
},
{
.irq_reg = BXTWC_THRM0IRQ,
.irq_mask = 0x10,
.irq_en = BXTWC_MTHRM0IRQ,
.irq_en_mask = 0x10,
.evt_stat = BXTWC_STHRM0IRQ,
.evt_mask = 0x10,
.trip_num = 1
}
};
static const struct trip_config_map bxtwc_str1_trip_config[] = {
{
.irq_reg = BXTWC_THRM0IRQ,
.irq_mask = 0x02,
.irq_en = BXTWC_MTHRM0IRQ,
.irq_en_mask = 0x02,
.evt_stat = BXTWC_STHRM0IRQ,
.evt_mask = 0x02,
.trip_num = 0
},
{
.irq_reg = BXTWC_THRM0IRQ,
.irq_mask = 0x20,
.irq_en = BXTWC_MTHRM0IRQ,
.irq_en_mask = 0x20,
.evt_stat = BXTWC_STHRM0IRQ,
.evt_mask = 0x20,
.trip_num = 1
},
};
static const struct trip_config_map bxtwc_str2_trip_config[] = {
{
.irq_reg = BXTWC_THRM0IRQ,
.irq_mask = 0x04,
.irq_en = BXTWC_MTHRM0IRQ,
.irq_en_mask = 0x04,
.evt_stat = BXTWC_STHRM0IRQ,
.evt_mask = 0x04,
.trip_num = 0
},
{
.irq_reg = BXTWC_THRM0IRQ,
.irq_mask = 0x40,
.irq_en = BXTWC_MTHRM0IRQ,
.irq_en_mask = 0x40,
.evt_stat = BXTWC_STHRM0IRQ,
.evt_mask = 0x40,
.trip_num = 1
},
};
static const struct trip_config_map bxtwc_str3_trip_config[] = {
{
.irq_reg = BXTWC_THRM2IRQ,
.irq_mask = 0x10,
.irq_en = BXTWC_MTHRM2IRQ,
.irq_en_mask = 0x10,
.evt_stat = BXTWC_STHRM2IRQ,
.evt_mask = 0x10,
.trip_num = 0
},
};
static const struct thermal_irq_map bxtwc_thermal_irq_map[] = {
{
.handle = "STR0",
.trip_config = bxtwc_str0_trip_config,
.num_trips = ARRAY_SIZE(bxtwc_str0_trip_config),
},
{
.handle = "STR1",
.trip_config = bxtwc_str1_trip_config,
.num_trips = ARRAY_SIZE(bxtwc_str1_trip_config),
},
{
.handle = "STR2",
.trip_config = bxtwc_str2_trip_config,
.num_trips = ARRAY_SIZE(bxtwc_str2_trip_config),
},
{
.handle = "STR3",
.trip_config = bxtwc_str3_trip_config,
.num_trips = ARRAY_SIZE(bxtwc_str3_trip_config),
},
};
static const struct pmic_thermal_data bxtwc_thermal_data = {
.maps = bxtwc_thermal_irq_map,
.num_maps = ARRAY_SIZE(bxtwc_thermal_irq_map),
};
static irqreturn_t pmic_thermal_irq_handler(int irq, void *data)
{
struct platform_device *pdev = data;
struct thermal_zone_device *tzd;
struct pmic_thermal_data *td;
struct intel_soc_pmic *pmic;
struct regmap *regmap;
u8 reg_val, mask, irq_stat;
u16 reg, evt_stat_reg;
int i, j, ret;
pmic = dev_get_drvdata(pdev->dev.parent);
regmap = pmic->regmap;
td = (struct pmic_thermal_data *)
platform_get_device_id(pdev)->driver_data;
/* Resolve thermal irqs */
for (i = 0; i < td->num_maps; i++) {
for (j = 0; j < td->maps[i].num_trips; j++) {
reg = td->maps[i].trip_config[j].irq_reg;
mask = td->maps[i].trip_config[j].irq_mask;
/*
* Read the irq register to resolve whether the
* interrupt was triggered for this sensor
*/
if (regmap_read(regmap, reg, &ret))
return IRQ_HANDLED;
reg_val = (u8)ret;
irq_stat = ((u8)ret & mask);
if (!irq_stat)
continue;
/*
* Read the status register to find out what
* event occurred i.e a high or a low
*/
evt_stat_reg = td->maps[i].trip_config[j].evt_stat;
if (regmap_read(regmap, evt_stat_reg, &ret))
return IRQ_HANDLED;
tzd = thermal_zone_get_zone_by_name(td->maps[i].handle);
if (!IS_ERR(tzd))
thermal_zone_device_update(tzd,
THERMAL_EVENT_UNSPECIFIED);
/* Clear the appropriate irq */
regmap_write(regmap, reg, reg_val & mask);
}
}
return IRQ_HANDLED;
}
static int pmic_thermal_probe(struct platform_device *pdev)
{
struct regmap_irq_chip_data *regmap_irq_chip;
struct pmic_thermal_data *thermal_data;
int ret, irq, virq, i, j, pmic_irq_count;
struct intel_soc_pmic *pmic;
struct regmap *regmap;
struct device *dev;
u16 reg;
u8 mask;
dev = &pdev->dev;
pmic = dev_get_drvdata(pdev->dev.parent);
if (!pmic) {
dev_err(dev, "Failed to get struct intel_soc_pmic pointer\n");
return -ENODEV;
}
thermal_data = (struct pmic_thermal_data *)
platform_get_device_id(pdev)->driver_data;
if (!thermal_data) {
dev_err(dev, "No thermal data initialized!!\n");
return -ENODEV;
}
regmap = pmic->regmap;
regmap_irq_chip = pmic->irq_chip_data;
pmic_irq_count = 0;
while ((irq = platform_get_irq(pdev, pmic_irq_count)) != -ENXIO) {
virq = regmap_irq_get_virq(regmap_irq_chip, irq);
if (virq < 0) {
dev_err(dev, "failed to get virq by irq %d\n", irq);
return virq;
}
ret = devm_request_threaded_irq(&pdev->dev, virq,
NULL, pmic_thermal_irq_handler,
IRQF_ONESHOT, "pmic_thermal", pdev);
if (ret) {
dev_err(dev, "request irq(%d) failed: %d\n", virq, ret);
return ret;
}
pmic_irq_count++;
}
/* Enable thermal interrupts */
for (i = 0; i < thermal_data->num_maps; i++) {
for (j = 0; j < thermal_data->maps[i].num_trips; j++) {
reg = thermal_data->maps[i].trip_config[j].irq_en;
mask = thermal_data->maps[i].trip_config[j].irq_en_mask;
ret = regmap_update_bits(regmap, reg, mask, 0x00);
if (ret)
return ret;
}
}
return 0;
}
static const struct platform_device_id pmic_thermal_id_table[] = {
{
.name = "bxt_wcove_thermal",
.driver_data = (kernel_ulong_t)&bxtwc_thermal_data,
},
{},
};
static struct platform_driver pmic_thermal_driver = {
.probe = pmic_thermal_probe,
.driver = {
.name = "pmic_thermal",
},
.id_table = pmic_thermal_id_table,
};
MODULE_DEVICE_TABLE(platform, pmic_thermal_id_table);
module_platform_driver(pmic_thermal_driver);
MODULE_AUTHOR("Yegnesh S Iyer <yegnesh.s.iyer@intel.com>");
MODULE_DESCRIPTION("Intel Broxton PMIC Thermal Driver");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,432 @@
/* intel_pch_thermal.c - Intel PCH Thermal driver
*
* Copyright (c) 2015, Intel Corporation.
*
* Authors:
* Tushar Dave <tushar.n.dave@intel.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <linux/module.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/pci.h>
#include <linux/acpi.h>
#include <linux/thermal.h>
#include <linux/pm.h>
/* Intel PCH thermal Device IDs */
#define PCH_THERMAL_DID_HSW_1 0x9C24 /* Haswell PCH */
#define PCH_THERMAL_DID_HSW_2 0x8C24 /* Haswell PCH */
#define PCH_THERMAL_DID_WPT 0x9CA4 /* Wildcat Point */
#define PCH_THERMAL_DID_SKL 0x9D31 /* Skylake PCH */
#define PCH_THERMAL_DID_SKL_H 0xA131 /* Skylake PCH 100 series */
#define PCH_THERMAL_DID_CNL 0x9Df9 /* CNL PCH */
#define PCH_THERMAL_DID_CNL_H 0xA379 /* CNL-H PCH */
/* Wildcat Point-LP PCH Thermal registers */
#define WPT_TEMP 0x0000 /* Temperature */
#define WPT_TSC 0x04 /* Thermal Sensor Control */
#define WPT_TSS 0x06 /* Thermal Sensor Status */
#define WPT_TSEL 0x08 /* Thermal Sensor Enable and Lock */
#define WPT_TSREL 0x0A /* Thermal Sensor Report Enable and Lock */
#define WPT_TSMIC 0x0C /* Thermal Sensor SMI Control */
#define WPT_CTT 0x0010 /* Catastrophic Trip Point */
#define WPT_TAHV 0x0014 /* Thermal Alert High Value */
#define WPT_TALV 0x0018 /* Thermal Alert Low Value */
#define WPT_TL 0x00000040 /* Throttle Value */
#define WPT_PHL 0x0060 /* PCH Hot Level */
#define WPT_PHLC 0x62 /* PHL Control */
#define WPT_TAS 0x80 /* Thermal Alert Status */
#define WPT_TSPIEN 0x82 /* PCI Interrupt Event Enables */
#define WPT_TSGPEN 0x84 /* General Purpose Event Enables */
/* Wildcat Point-LP PCH Thermal Register bit definitions */
#define WPT_TEMP_TSR 0x01ff /* Temp TS Reading */
#define WPT_TSC_CPDE 0x01 /* Catastrophic Power-Down Enable */
#define WPT_TSS_TSDSS 0x10 /* Thermal Sensor Dynamic Shutdown Status */
#define WPT_TSS_GPES 0x08 /* GPE status */
#define WPT_TSEL_ETS 0x01 /* Enable TS */
#define WPT_TSEL_PLDB 0x80 /* TSEL Policy Lock-Down Bit */
#define WPT_TL_TOL 0x000001FF /* T0 Level */
#define WPT_TL_T1L 0x1ff00000 /* T1 Level */
#define WPT_TL_TTEN 0x20000000 /* TT Enable */
static char driver_name[] = "Intel PCH thermal driver";
struct pch_thermal_device {
void __iomem *hw_base;
const struct pch_dev_ops *ops;
struct pci_dev *pdev;
struct thermal_zone_device *tzd;
int crt_trip_id;
unsigned long crt_temp;
int hot_trip_id;
unsigned long hot_temp;
int psv_trip_id;
unsigned long psv_temp;
bool bios_enabled;
};
#ifdef CONFIG_ACPI
/*
* On some platforms, there is a companion ACPI device, which adds
* passive trip temperature using _PSV method. There is no specific
* passive temperature setting in MMIO interface of this PCI device.
*/
static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
int *nr_trips)
{
struct acpi_device *adev;
ptd->psv_trip_id = -1;
adev = ACPI_COMPANION(&ptd->pdev->dev);
if (adev) {
unsigned long long r;
acpi_status status;
status = acpi_evaluate_integer(adev->handle, "_PSV", NULL,
&r);
if (ACPI_SUCCESS(status)) {
unsigned long trip_temp;
trip_temp = DECI_KELVIN_TO_MILLICELSIUS(r);
if (trip_temp) {
ptd->psv_temp = trip_temp;
ptd->psv_trip_id = *nr_trips;
++(*nr_trips);
}
}
}
}
#else
static void pch_wpt_add_acpi_psv_trip(struct pch_thermal_device *ptd,
int *nr_trips)
{
ptd->psv_trip_id = -1;
}
#endif
static int pch_wpt_init(struct pch_thermal_device *ptd, int *nr_trips)
{
u8 tsel;
u16 trip_temp;
*nr_trips = 0;
/* Check if BIOS has already enabled thermal sensor */
if (WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL)) {
ptd->bios_enabled = true;
goto read_trips;
}
tsel = readb(ptd->hw_base + WPT_TSEL);
/*
* When TSEL's Policy Lock-Down bit is 1, TSEL become RO.
* If so, thermal sensor cannot enable. Bail out.
*/
if (tsel & WPT_TSEL_PLDB) {
dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
return -ENODEV;
}
writeb(tsel|WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL);
if (!(WPT_TSEL_ETS & readb(ptd->hw_base + WPT_TSEL))) {
dev_err(&ptd->pdev->dev, "Sensor can't be enabled\n");
return -ENODEV;
}
read_trips:
ptd->crt_trip_id = -1;
trip_temp = readw(ptd->hw_base + WPT_CTT);
trip_temp &= 0x1FF;
if (trip_temp) {
/* Resolution of 1/2 degree C and an offset of -50C */
ptd->crt_temp = trip_temp * 1000 / 2 - 50000;
ptd->crt_trip_id = 0;
++(*nr_trips);
}
ptd->hot_trip_id = -1;
trip_temp = readw(ptd->hw_base + WPT_PHL);
trip_temp &= 0x1FF;
if (trip_temp) {
/* Resolution of 1/2 degree C and an offset of -50C */
ptd->hot_temp = trip_temp * 1000 / 2 - 50000;
ptd->hot_trip_id = *nr_trips;
++(*nr_trips);
}
pch_wpt_add_acpi_psv_trip(ptd, nr_trips);
return 0;
}
static int pch_wpt_get_temp(struct pch_thermal_device *ptd, int *temp)
{
u16 wpt_temp;
wpt_temp = WPT_TEMP_TSR & readw(ptd->hw_base + WPT_TEMP);
/* Resolution of 1/2 degree C and an offset of -50C */
*temp = (wpt_temp * 1000 / 2 - 50000);
return 0;
}
static int pch_wpt_suspend(struct pch_thermal_device *ptd)
{
u8 tsel;
if (ptd->bios_enabled)
return 0;
tsel = readb(ptd->hw_base + WPT_TSEL);
writeb(tsel & 0xFE, ptd->hw_base + WPT_TSEL);
return 0;
}
static int pch_wpt_resume(struct pch_thermal_device *ptd)
{
u8 tsel;
if (ptd->bios_enabled)
return 0;
tsel = readb(ptd->hw_base + WPT_TSEL);
writeb(tsel | WPT_TSEL_ETS, ptd->hw_base + WPT_TSEL);
return 0;
}
struct pch_dev_ops {
int (*hw_init)(struct pch_thermal_device *ptd, int *nr_trips);
int (*get_temp)(struct pch_thermal_device *ptd, int *temp);
int (*suspend)(struct pch_thermal_device *ptd);
int (*resume)(struct pch_thermal_device *ptd);
};
/* dev ops for Wildcat Point */
static const struct pch_dev_ops pch_dev_ops_wpt = {
.hw_init = pch_wpt_init,
.get_temp = pch_wpt_get_temp,
.suspend = pch_wpt_suspend,
.resume = pch_wpt_resume,
};
static int pch_thermal_get_temp(struct thermal_zone_device *tzd, int *temp)
{
struct pch_thermal_device *ptd = tzd->devdata;
return ptd->ops->get_temp(ptd, temp);
}
static int pch_get_trip_type(struct thermal_zone_device *tzd, int trip,
enum thermal_trip_type *type)
{
struct pch_thermal_device *ptd = tzd->devdata;
if (ptd->crt_trip_id == trip)
*type = THERMAL_TRIP_CRITICAL;
else if (ptd->hot_trip_id == trip)
*type = THERMAL_TRIP_HOT;
else if (ptd->psv_trip_id == trip)
*type = THERMAL_TRIP_PASSIVE;
else
return -EINVAL;
return 0;
}
static int pch_get_trip_temp(struct thermal_zone_device *tzd, int trip, int *temp)
{
struct pch_thermal_device *ptd = tzd->devdata;
if (ptd->crt_trip_id == trip)
*temp = ptd->crt_temp;
else if (ptd->hot_trip_id == trip)
*temp = ptd->hot_temp;
else if (ptd->psv_trip_id == trip)
*temp = ptd->psv_temp;
else
return -EINVAL;
return 0;
}
static struct thermal_zone_device_ops tzd_ops = {
.get_temp = pch_thermal_get_temp,
.get_trip_type = pch_get_trip_type,
.get_trip_temp = pch_get_trip_temp,
};
enum board_ids {
board_hsw,
board_wpt,
board_skl,
board_cnl,
};
static const struct board_info {
const char *name;
const struct pch_dev_ops *ops;
} board_info[] = {
[board_hsw] = {
.name = "pch_haswell",
.ops = &pch_dev_ops_wpt,
},
[board_wpt] = {
.name = "pch_wildcat_point",
.ops = &pch_dev_ops_wpt,
},
[board_skl] = {
.name = "pch_skylake",
.ops = &pch_dev_ops_wpt,
},
[board_cnl] = {
.name = "pch_cannonlake",
.ops = &pch_dev_ops_wpt,
},
};
static int intel_pch_thermal_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
enum board_ids board_id = id->driver_data;
const struct board_info *bi = &board_info[board_id];
struct pch_thermal_device *ptd;
int err;
int nr_trips;
ptd = devm_kzalloc(&pdev->dev, sizeof(*ptd), GFP_KERNEL);
if (!ptd)
return -ENOMEM;
ptd->ops = bi->ops;
pci_set_drvdata(pdev, ptd);
ptd->pdev = pdev;
err = pci_enable_device(pdev);
if (err) {
dev_err(&pdev->dev, "failed to enable pci device\n");
return err;
}
err = pci_request_regions(pdev, driver_name);
if (err) {
dev_err(&pdev->dev, "failed to request pci region\n");
goto error_disable;
}
ptd->hw_base = pci_ioremap_bar(pdev, 0);
if (!ptd->hw_base) {
err = -ENOMEM;
dev_err(&pdev->dev, "failed to map mem base\n");
goto error_release;
}
err = ptd->ops->hw_init(ptd, &nr_trips);
if (err)
goto error_cleanup;
ptd->tzd = thermal_zone_device_register(bi->name, nr_trips, 0, ptd,
&tzd_ops, NULL, 0, 0);
if (IS_ERR(ptd->tzd)) {
dev_err(&pdev->dev, "Failed to register thermal zone %s\n",
bi->name);
err = PTR_ERR(ptd->tzd);
goto error_cleanup;
}
return 0;
error_cleanup:
iounmap(ptd->hw_base);
error_release:
pci_release_regions(pdev);
error_disable:
pci_disable_device(pdev);
dev_err(&pdev->dev, "pci device failed to probe\n");
return err;
}
static void intel_pch_thermal_remove(struct pci_dev *pdev)
{
struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
thermal_zone_device_unregister(ptd->tzd);
iounmap(ptd->hw_base);
pci_set_drvdata(pdev, NULL);
pci_release_region(pdev, 0);
pci_disable_device(pdev);
}
static int intel_pch_thermal_suspend(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
return ptd->ops->suspend(ptd);
}
static int intel_pch_thermal_resume(struct device *device)
{
struct pci_dev *pdev = to_pci_dev(device);
struct pch_thermal_device *ptd = pci_get_drvdata(pdev);
return ptd->ops->resume(ptd);
}
static const struct pci_device_id intel_pch_thermal_id[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_1),
.driver_data = board_hsw, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_HSW_2),
.driver_data = board_hsw, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_WPT),
.driver_data = board_wpt, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL),
.driver_data = board_skl, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_SKL_H),
.driver_data = board_skl, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL),
.driver_data = board_cnl, },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCH_THERMAL_DID_CNL_H),
.driver_data = board_cnl, },
{ 0, },
};
MODULE_DEVICE_TABLE(pci, intel_pch_thermal_id);
static const struct dev_pm_ops intel_pch_pm_ops = {
.suspend = intel_pch_thermal_suspend,
.resume = intel_pch_thermal_resume,
};
static struct pci_driver intel_pch_thermal_driver = {
.name = "intel_pch_thermal",
.id_table = intel_pch_thermal_id,
.probe = intel_pch_thermal_probe,
.remove = intel_pch_thermal_remove,
.driver.pm = &intel_pch_pm_ops,
};
module_pci_driver(intel_pch_thermal_driver);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Intel PCH Thermal driver");

View File

@@ -0,0 +1,803 @@
/*
* intel_powerclamp.c - package c-state idle injection
*
* Copyright (c) 2012, Intel Corporation.
*
* Authors:
* Arjan van de Ven <arjan@linux.intel.com>
* Jacob Pan <jacob.jun.pan@linux.intel.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*
* TODO:
* 1. better handle wakeup from external interrupts, currently a fixed
* compensation is added to clamping duration when excessive amount
* of wakeups are observed during idle time. the reason is that in
* case of external interrupts without need for ack, clamping down
* cpu in non-irq context does not reduce irq. for majority of the
* cases, clamping down cpu does help reduce irq as well, we should
* be able to differentiate the two cases and give a quantitative
* solution for the irqs that we can control. perhaps based on
* get_cpu_iowait_time_us()
*
* 2. synchronization with other hw blocks
*
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/cpu.h>
#include <linux/thermal.h>
#include <linux/slab.h>
#include <linux/tick.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <linux/sched/rt.h>
#include <uapi/linux/sched/types.h>
#include <asm/nmi.h>
#include <asm/msr.h>
#include <asm/mwait.h>
#include <asm/cpu_device_id.h>
#include <asm/hardirq.h>
#define MAX_TARGET_RATIO (50U)
/* For each undisturbed clamping period (no extra wake ups during idle time),
* we increment the confidence counter for the given target ratio.
* CONFIDENCE_OK defines the level where runtime calibration results are
* valid.
*/
#define CONFIDENCE_OK (3)
/* Default idle injection duration, driver adjust sleep time to meet target
* idle ratio. Similar to frequency modulation.
*/
#define DEFAULT_DURATION_JIFFIES (6)
static unsigned int target_mwait;
static struct dentry *debug_dir;
/* user selected target */
static unsigned int set_target_ratio;
static unsigned int current_ratio;
static bool should_skip;
static bool reduce_irq;
static atomic_t idle_wakeup_counter;
static unsigned int control_cpu; /* The cpu assigned to collect stat and update
* control parameters. default to BSP but BSP
* can be offlined.
*/
static bool clamping;
static const struct sched_param sparam = {
.sched_priority = MAX_USER_RT_PRIO / 2,
};
struct powerclamp_worker_data {
struct kthread_worker *worker;
struct kthread_work balancing_work;
struct kthread_delayed_work idle_injection_work;
unsigned int cpu;
unsigned int count;
unsigned int guard;
unsigned int window_size_now;
unsigned int target_ratio;
unsigned int duration_jiffies;
bool clamping;
};
static struct powerclamp_worker_data * __percpu worker_data;
static struct thermal_cooling_device *cooling_dev;
static unsigned long *cpu_clamping_mask; /* bit map for tracking per cpu
* clamping kthread worker
*/
static unsigned int duration;
static unsigned int pkg_cstate_ratio_cur;
static unsigned int window_size;
static int duration_set(const char *arg, const struct kernel_param *kp)
{
int ret = 0;
unsigned long new_duration;
ret = kstrtoul(arg, 10, &new_duration);
if (ret)
goto exit;
if (new_duration > 25 || new_duration < 6) {
pr_err("Out of recommended range %lu, between 6-25ms\n",
new_duration);
ret = -EINVAL;
}
duration = clamp(new_duration, 6ul, 25ul);
smp_mb();
exit:
return ret;
}
static const struct kernel_param_ops duration_ops = {
.set = duration_set,
.get = param_get_int,
};
module_param_cb(duration, &duration_ops, &duration, 0644);
MODULE_PARM_DESC(duration, "forced idle time for each attempt in msec.");
struct powerclamp_calibration_data {
unsigned long confidence; /* used for calibration, basically a counter
* gets incremented each time a clamping
* period is completed without extra wakeups
* once that counter is reached given level,
* compensation is deemed usable.
*/
unsigned long steady_comp; /* steady state compensation used when
* no extra wakeups occurred.
*/
unsigned long dynamic_comp; /* compensate excessive wakeup from idle
* mostly from external interrupts.
*/
};
static struct powerclamp_calibration_data cal_data[MAX_TARGET_RATIO];
static int window_size_set(const char *arg, const struct kernel_param *kp)
{
int ret = 0;
unsigned long new_window_size;
ret = kstrtoul(arg, 10, &new_window_size);
if (ret)
goto exit_win;
if (new_window_size > 10 || new_window_size < 2) {
pr_err("Out of recommended window size %lu, between 2-10\n",
new_window_size);
ret = -EINVAL;
}
window_size = clamp(new_window_size, 2ul, 10ul);
smp_mb();
exit_win:
return ret;
}
static const struct kernel_param_ops window_size_ops = {
.set = window_size_set,
.get = param_get_int,
};
module_param_cb(window_size, &window_size_ops, &window_size, 0644);
MODULE_PARM_DESC(window_size, "sliding window in number of clamping cycles\n"
"\tpowerclamp controls idle ratio within this window. larger\n"
"\twindow size results in slower response time but more smooth\n"
"\tclamping results. default to 2.");
static void find_target_mwait(void)
{
unsigned int eax, ebx, ecx, edx;
unsigned int highest_cstate = 0;
unsigned int highest_subcstate = 0;
int i;
if (boot_cpu_data.cpuid_level < CPUID_MWAIT_LEAF)
return;
cpuid(CPUID_MWAIT_LEAF, &eax, &ebx, &ecx, &edx);
if (!(ecx & CPUID5_ECX_EXTENSIONS_SUPPORTED) ||
!(ecx & CPUID5_ECX_INTERRUPT_BREAK))
return;
edx >>= MWAIT_SUBSTATE_SIZE;
for (i = 0; i < 7 && edx; i++, edx >>= MWAIT_SUBSTATE_SIZE) {
if (edx & MWAIT_SUBSTATE_MASK) {
highest_cstate = i;
highest_subcstate = edx & MWAIT_SUBSTATE_MASK;
}
}
target_mwait = (highest_cstate << MWAIT_SUBSTATE_SIZE) |
(highest_subcstate - 1);
}
struct pkg_cstate_info {
bool skip;
int msr_index;
int cstate_id;
};
#define PKG_CSTATE_INIT(id) { \
.msr_index = MSR_PKG_C##id##_RESIDENCY, \
.cstate_id = id \
}
static struct pkg_cstate_info pkg_cstates[] = {
PKG_CSTATE_INIT(2),
PKG_CSTATE_INIT(3),
PKG_CSTATE_INIT(6),
PKG_CSTATE_INIT(7),
PKG_CSTATE_INIT(8),
PKG_CSTATE_INIT(9),
PKG_CSTATE_INIT(10),
{NULL},
};
static bool has_pkg_state_counter(void)
{
u64 val;
struct pkg_cstate_info *info = pkg_cstates;
/* check if any one of the counter msrs exists */
while (info->msr_index) {
if (!rdmsrl_safe(info->msr_index, &val))
return true;
info++;
}
return false;
}
static u64 pkg_state_counter(void)
{
u64 val;
u64 count = 0;
struct pkg_cstate_info *info = pkg_cstates;
while (info->msr_index) {
if (!info->skip) {
if (!rdmsrl_safe(info->msr_index, &val))
count += val;
else
info->skip = true;
}
info++;
}
return count;
}
static unsigned int get_compensation(int ratio)
{
unsigned int comp = 0;
/* we only use compensation if all adjacent ones are good */
if (ratio == 1 &&
cal_data[ratio].confidence >= CONFIDENCE_OK &&
cal_data[ratio + 1].confidence >= CONFIDENCE_OK &&
cal_data[ratio + 2].confidence >= CONFIDENCE_OK) {
comp = (cal_data[ratio].steady_comp +
cal_data[ratio + 1].steady_comp +
cal_data[ratio + 2].steady_comp) / 3;
} else if (ratio == MAX_TARGET_RATIO - 1 &&
cal_data[ratio].confidence >= CONFIDENCE_OK &&
cal_data[ratio - 1].confidence >= CONFIDENCE_OK &&
cal_data[ratio - 2].confidence >= CONFIDENCE_OK) {
comp = (cal_data[ratio].steady_comp +
cal_data[ratio - 1].steady_comp +
cal_data[ratio - 2].steady_comp) / 3;
} else if (cal_data[ratio].confidence >= CONFIDENCE_OK &&
cal_data[ratio - 1].confidence >= CONFIDENCE_OK &&
cal_data[ratio + 1].confidence >= CONFIDENCE_OK) {
comp = (cal_data[ratio].steady_comp +
cal_data[ratio - 1].steady_comp +
cal_data[ratio + 1].steady_comp) / 3;
}
/* REVISIT: simple penalty of double idle injection */
if (reduce_irq)
comp = ratio;
/* do not exceed limit */
if (comp + ratio >= MAX_TARGET_RATIO)
comp = MAX_TARGET_RATIO - ratio - 1;
return comp;
}
static void adjust_compensation(int target_ratio, unsigned int win)
{
int delta;
struct powerclamp_calibration_data *d = &cal_data[target_ratio];
/*
* adjust compensations if confidence level has not been reached or
* there are too many wakeups during the last idle injection period, we
* cannot trust the data for compensation.
*/
if (d->confidence >= CONFIDENCE_OK ||
atomic_read(&idle_wakeup_counter) >
win * num_online_cpus())
return;
delta = set_target_ratio - current_ratio;
/* filter out bad data */
if (delta >= 0 && delta <= (1+target_ratio/10)) {
if (d->steady_comp)
d->steady_comp =
roundup(delta+d->steady_comp, 2)/2;
else
d->steady_comp = delta;
d->confidence++;
}
}
static bool powerclamp_adjust_controls(unsigned int target_ratio,
unsigned int guard, unsigned int win)
{
static u64 msr_last, tsc_last;
u64 msr_now, tsc_now;
u64 val64;
/* check result for the last window */
msr_now = pkg_state_counter();
tsc_now = rdtsc();
/* calculate pkg cstate vs tsc ratio */
if (!msr_last || !tsc_last)
current_ratio = 1;
else if (tsc_now-tsc_last) {
val64 = 100*(msr_now-msr_last);
do_div(val64, (tsc_now-tsc_last));
current_ratio = val64;
}
/* update record */
msr_last = msr_now;
tsc_last = tsc_now;
adjust_compensation(target_ratio, win);
/*
* too many external interrupts, set flag such
* that we can take measure later.
*/
reduce_irq = atomic_read(&idle_wakeup_counter) >=
2 * win * num_online_cpus();
atomic_set(&idle_wakeup_counter, 0);
/* if we are above target+guard, skip */
return set_target_ratio + guard <= current_ratio;
}
static void clamp_balancing_func(struct kthread_work *work)
{
struct powerclamp_worker_data *w_data;
int sleeptime;
unsigned long target_jiffies;
unsigned int compensated_ratio;
int interval; /* jiffies to sleep for each attempt */
w_data = container_of(work, struct powerclamp_worker_data,
balancing_work);
/*
* make sure user selected ratio does not take effect until
* the next round. adjust target_ratio if user has changed
* target such that we can converge quickly.
*/
w_data->target_ratio = READ_ONCE(set_target_ratio);
w_data->guard = 1 + w_data->target_ratio / 20;
w_data->window_size_now = window_size;
w_data->duration_jiffies = msecs_to_jiffies(duration);
w_data->count++;
/*
* systems may have different ability to enter package level
* c-states, thus we need to compensate the injected idle ratio
* to achieve the actual target reported by the HW.
*/
compensated_ratio = w_data->target_ratio +
get_compensation(w_data->target_ratio);
if (compensated_ratio <= 0)
compensated_ratio = 1;
interval = w_data->duration_jiffies * 100 / compensated_ratio;
/* align idle time */
target_jiffies = roundup(jiffies, interval);
sleeptime = target_jiffies - jiffies;
if (sleeptime <= 0)
sleeptime = 1;
if (clamping && w_data->clamping && cpu_online(w_data->cpu))
kthread_queue_delayed_work(w_data->worker,
&w_data->idle_injection_work,
sleeptime);
}
static void clamp_idle_injection_func(struct kthread_work *work)
{
struct powerclamp_worker_data *w_data;
w_data = container_of(work, struct powerclamp_worker_data,
idle_injection_work.work);
/*
* only elected controlling cpu can collect stats and update
* control parameters.
*/
if (w_data->cpu == control_cpu &&
!(w_data->count % w_data->window_size_now)) {
should_skip =
powerclamp_adjust_controls(w_data->target_ratio,
w_data->guard,
w_data->window_size_now);
smp_mb();
}
if (should_skip)
goto balance;
play_idle(jiffies_to_msecs(w_data->duration_jiffies));
balance:
if (clamping && w_data->clamping && cpu_online(w_data->cpu))
kthread_queue_work(w_data->worker, &w_data->balancing_work);
}
/*
* 1 HZ polling while clamping is active, useful for userspace
* to monitor actual idle ratio.
*/
static void poll_pkg_cstate(struct work_struct *dummy);
static DECLARE_DELAYED_WORK(poll_pkg_cstate_work, poll_pkg_cstate);
static void poll_pkg_cstate(struct work_struct *dummy)
{
static u64 msr_last;
static u64 tsc_last;
u64 msr_now;
u64 tsc_now;
u64 val64;
msr_now = pkg_state_counter();
tsc_now = rdtsc();
/* calculate pkg cstate vs tsc ratio */
if (!msr_last || !tsc_last)
pkg_cstate_ratio_cur = 1;
else {
if (tsc_now - tsc_last) {
val64 = 100 * (msr_now - msr_last);
do_div(val64, (tsc_now - tsc_last));
pkg_cstate_ratio_cur = val64;
}
}
/* update record */
msr_last = msr_now;
tsc_last = tsc_now;
if (true == clamping)
schedule_delayed_work(&poll_pkg_cstate_work, HZ);
}
static void start_power_clamp_worker(unsigned long cpu)
{
struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu);
struct kthread_worker *worker;
worker = kthread_create_worker_on_cpu(cpu, 0, "kidle_inject/%ld", cpu);
if (IS_ERR(worker))
return;
w_data->worker = worker;
w_data->count = 0;
w_data->cpu = cpu;
w_data->clamping = true;
set_bit(cpu, cpu_clamping_mask);
sched_setscheduler(worker->task, SCHED_FIFO, &sparam);
kthread_init_work(&w_data->balancing_work, clamp_balancing_func);
kthread_init_delayed_work(&w_data->idle_injection_work,
clamp_idle_injection_func);
kthread_queue_work(w_data->worker, &w_data->balancing_work);
}
static void stop_power_clamp_worker(unsigned long cpu)
{
struct powerclamp_worker_data *w_data = per_cpu_ptr(worker_data, cpu);
if (!w_data->worker)
return;
w_data->clamping = false;
/*
* Make sure that all works that get queued after this point see
* the clamping disabled. The counter part is not needed because
* there is an implicit memory barrier when the queued work
* is proceed.
*/
smp_wmb();
kthread_cancel_work_sync(&w_data->balancing_work);
kthread_cancel_delayed_work_sync(&w_data->idle_injection_work);
/*
* The balancing work still might be queued here because
* the handling of the "clapming" variable, cancel, and queue
* operations are not synchronized via a lock. But it is not
* a big deal. The balancing work is fast and destroy kthread
* will wait for it.
*/
clear_bit(w_data->cpu, cpu_clamping_mask);
kthread_destroy_worker(w_data->worker);
w_data->worker = NULL;
}
static int start_power_clamp(void)
{
unsigned long cpu;
set_target_ratio = clamp(set_target_ratio, 0U, MAX_TARGET_RATIO - 1);
/* prevent cpu hotplug */
get_online_cpus();
/* prefer BSP */
control_cpu = 0;
if (!cpu_online(control_cpu))
control_cpu = smp_processor_id();
clamping = true;
schedule_delayed_work(&poll_pkg_cstate_work, 0);
/* start one kthread worker per online cpu */
for_each_online_cpu(cpu) {
start_power_clamp_worker(cpu);
}
put_online_cpus();
return 0;
}
static void end_power_clamp(void)
{
int i;
/*
* Block requeuing in all the kthread workers. They will flush and
* stop faster.
*/
clamping = false;
if (bitmap_weight(cpu_clamping_mask, num_possible_cpus())) {
for_each_set_bit(i, cpu_clamping_mask, num_possible_cpus()) {
pr_debug("clamping worker for cpu %d alive, destroy\n",
i);
stop_power_clamp_worker(i);
}
}
}
static int powerclamp_cpu_online(unsigned int cpu)
{
if (clamping == false)
return 0;
start_power_clamp_worker(cpu);
/* prefer BSP as controlling CPU */
if (cpu == 0) {
control_cpu = 0;
smp_mb();
}
return 0;
}
static int powerclamp_cpu_predown(unsigned int cpu)
{
if (clamping == false)
return 0;
stop_power_clamp_worker(cpu);
if (cpu != control_cpu)
return 0;
control_cpu = cpumask_first(cpu_online_mask);
if (control_cpu == cpu)
control_cpu = cpumask_next(cpu, cpu_online_mask);
smp_mb();
return 0;
}
static int powerclamp_get_max_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
*state = MAX_TARGET_RATIO;
return 0;
}
static int powerclamp_get_cur_state(struct thermal_cooling_device *cdev,
unsigned long *state)
{
if (true == clamping)
*state = pkg_cstate_ratio_cur;
else
/* to save power, do not poll idle ratio while not clamping */
*state = -1; /* indicates invalid state */
return 0;
}
static int powerclamp_set_cur_state(struct thermal_cooling_device *cdev,
unsigned long new_target_ratio)
{
int ret = 0;
new_target_ratio = clamp(new_target_ratio, 0UL,
(unsigned long) (MAX_TARGET_RATIO-1));
if (set_target_ratio == 0 && new_target_ratio > 0) {
pr_info("Start idle injection to reduce power\n");
set_target_ratio = new_target_ratio;
ret = start_power_clamp();
goto exit_set;
} else if (set_target_ratio > 0 && new_target_ratio == 0) {
pr_info("Stop forced idle injection\n");
end_power_clamp();
set_target_ratio = 0;
} else /* adjust currently running */ {
set_target_ratio = new_target_ratio;
/* make new set_target_ratio visible to other cpus */
smp_mb();
}
exit_set:
return ret;
}
/* bind to generic thermal layer as cooling device*/
static struct thermal_cooling_device_ops powerclamp_cooling_ops = {
.get_max_state = powerclamp_get_max_state,
.get_cur_state = powerclamp_get_cur_state,
.set_cur_state = powerclamp_set_cur_state,
};
static const struct x86_cpu_id __initconst intel_powerclamp_ids[] = {
{ X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_MWAIT },
{}
};
MODULE_DEVICE_TABLE(x86cpu, intel_powerclamp_ids);
static int __init powerclamp_probe(void)
{
if (!x86_match_cpu(intel_powerclamp_ids)) {
pr_err("CPU does not support MWAIT\n");
return -ENODEV;
}
/* The goal for idle time alignment is to achieve package cstate. */
if (!has_pkg_state_counter()) {
pr_info("No package C-state available\n");
return -ENODEV;
}
/* find the deepest mwait value */
find_target_mwait();
return 0;
}
static int powerclamp_debug_show(struct seq_file *m, void *unused)
{
int i = 0;
seq_printf(m, "controlling cpu: %d\n", control_cpu);
seq_printf(m, "pct confidence steady dynamic (compensation)\n");
for (i = 0; i < MAX_TARGET_RATIO; i++) {
seq_printf(m, "%d\t%lu\t%lu\t%lu\n",
i,
cal_data[i].confidence,
cal_data[i].steady_comp,
cal_data[i].dynamic_comp);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(powerclamp_debug);
static inline void powerclamp_create_debug_files(void)
{
debug_dir = debugfs_create_dir("intel_powerclamp", NULL);
if (!debug_dir)
return;
if (!debugfs_create_file("powerclamp_calib", S_IRUGO, debug_dir,
cal_data, &powerclamp_debug_fops))
goto file_error;
return;
file_error:
debugfs_remove_recursive(debug_dir);
}
static enum cpuhp_state hp_state;
static int __init powerclamp_init(void)
{
int retval;
int bitmap_size;
bitmap_size = BITS_TO_LONGS(num_possible_cpus()) * sizeof(long);
cpu_clamping_mask = kzalloc(bitmap_size, GFP_KERNEL);
if (!cpu_clamping_mask)
return -ENOMEM;
/* probe cpu features and ids here */
retval = powerclamp_probe();
if (retval)
goto exit_free;
/* set default limit, maybe adjusted during runtime based on feedback */
window_size = 2;
retval = cpuhp_setup_state_nocalls(CPUHP_AP_ONLINE_DYN,
"thermal/intel_powerclamp:online",
powerclamp_cpu_online,
powerclamp_cpu_predown);
if (retval < 0)
goto exit_free;
hp_state = retval;
worker_data = alloc_percpu(struct powerclamp_worker_data);
if (!worker_data) {
retval = -ENOMEM;
goto exit_unregister;
}
cooling_dev = thermal_cooling_device_register("intel_powerclamp", NULL,
&powerclamp_cooling_ops);
if (IS_ERR(cooling_dev)) {
retval = -ENODEV;
goto exit_free_thread;
}
if (!duration)
duration = jiffies_to_msecs(DEFAULT_DURATION_JIFFIES);
powerclamp_create_debug_files();
return 0;
exit_free_thread:
free_percpu(worker_data);
exit_unregister:
cpuhp_remove_state_nocalls(hp_state);
exit_free:
kfree(cpu_clamping_mask);
return retval;
}
module_init(powerclamp_init);
static void __exit powerclamp_exit(void)
{
end_power_clamp();
cpuhp_remove_state_nocalls(hp_state);
free_percpu(worker_data);
thermal_cooling_device_unregister(cooling_dev);
kfree(cpu_clamping_mask);
cancel_delayed_work_sync(&poll_pkg_cstate_work);
debugfs_remove_recursive(debug_dir);
}
module_exit(powerclamp_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Arjan van de Ven <arjan@linux.intel.com>");
MODULE_AUTHOR("Jacob Pan <jacob.jun.pan@linux.intel.com>");
MODULE_DESCRIPTION("Package Level C-state Idle Injection for Intel CPUs");

View File

@@ -0,0 +1,471 @@
/*
* intel_quark_dts_thermal.c
*
* This file is provided under a dual BSD/GPLv2 license. When using or
* redistributing this file, you may do so under either license.
*
* GPL LICENSE SUMMARY
*
* Copyright(c) 2015 Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* Contact Information:
* Ong Boon Leong <boon.leong.ong@intel.com>
* Intel Malaysia, Penang
*
* BSD LICENSE
*
* Copyright(c) 2015 Intel Corporation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Quark DTS thermal driver is implemented by referencing
* intel_soc_dts_thermal.c.
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/thermal.h>
#include <asm/cpu_device_id.h>
#include <asm/iosf_mbi.h>
#define X86_FAMILY_QUARK 0x5
#define X86_MODEL_QUARK_X1000 0x9
/* DTS reset is programmed via QRK_MBI_UNIT_SOC */
#define QRK_DTS_REG_OFFSET_RESET 0x34
#define QRK_DTS_RESET_BIT BIT(0)
/* DTS enable is programmed via QRK_MBI_UNIT_RMU */
#define QRK_DTS_REG_OFFSET_ENABLE 0xB0
#define QRK_DTS_ENABLE_BIT BIT(15)
/* Temperature Register is read via QRK_MBI_UNIT_RMU */
#define QRK_DTS_REG_OFFSET_TEMP 0xB1
#define QRK_DTS_MASK_TEMP 0xFF
#define QRK_DTS_OFFSET_TEMP 0
#define QRK_DTS_OFFSET_REL_TEMP 16
#define QRK_DTS_TEMP_BASE 50
/* Programmable Trip Point Register is configured via QRK_MBI_UNIT_RMU */
#define QRK_DTS_REG_OFFSET_PTPS 0xB2
#define QRK_DTS_MASK_TP_THRES 0xFF
#define QRK_DTS_SHIFT_TP 8
#define QRK_DTS_ID_TP_CRITICAL 0
#define QRK_DTS_SAFE_TP_THRES 105
/* Thermal Sensor Register Lock */
#define QRK_DTS_REG_OFFSET_LOCK 0x71
#define QRK_DTS_LOCK_BIT BIT(5)
/* Quark DTS has 2 trip points: hot & catastrophic */
#define QRK_MAX_DTS_TRIPS 2
/* If DTS not locked, all trip points are configurable */
#define QRK_DTS_WR_MASK_SET 0x3
/* If DTS locked, all trip points are not configurable */
#define QRK_DTS_WR_MASK_CLR 0
#define DEFAULT_POLL_DELAY 2000
struct soc_sensor_entry {
bool locked;
u32 store_ptps;
u32 store_dts_enable;
enum thermal_device_mode mode;
struct thermal_zone_device *tzone;
};
static struct soc_sensor_entry *soc_dts;
static int polling_delay = DEFAULT_POLL_DELAY;
module_param(polling_delay, int, 0644);
MODULE_PARM_DESC(polling_delay,
"Polling interval for checking trip points (in milliseconds)");
static DEFINE_MUTEX(dts_update_mutex);
static int soc_dts_enable(struct thermal_zone_device *tzd)
{
u32 out;
struct soc_sensor_entry *aux_entry = tzd->devdata;
int ret;
ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
QRK_DTS_REG_OFFSET_ENABLE, &out);
if (ret)
return ret;
if (out & QRK_DTS_ENABLE_BIT) {
aux_entry->mode = THERMAL_DEVICE_ENABLED;
return 0;
}
if (!aux_entry->locked) {
out |= QRK_DTS_ENABLE_BIT;
ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
QRK_DTS_REG_OFFSET_ENABLE, out);
if (ret)
return ret;
aux_entry->mode = THERMAL_DEVICE_ENABLED;
} else {
aux_entry->mode = THERMAL_DEVICE_DISABLED;
pr_info("DTS is locked. Cannot enable DTS\n");
ret = -EPERM;
}
return ret;
}
static int soc_dts_disable(struct thermal_zone_device *tzd)
{
u32 out;
struct soc_sensor_entry *aux_entry = tzd->devdata;
int ret;
ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
QRK_DTS_REG_OFFSET_ENABLE, &out);
if (ret)
return ret;
if (!(out & QRK_DTS_ENABLE_BIT)) {
aux_entry->mode = THERMAL_DEVICE_DISABLED;
return 0;
}
if (!aux_entry->locked) {
out &= ~QRK_DTS_ENABLE_BIT;
ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
QRK_DTS_REG_OFFSET_ENABLE, out);
if (ret)
return ret;
aux_entry->mode = THERMAL_DEVICE_DISABLED;
} else {
aux_entry->mode = THERMAL_DEVICE_ENABLED;
pr_info("DTS is locked. Cannot disable DTS\n");
ret = -EPERM;
}
return ret;
}
static int _get_trip_temp(int trip, int *temp)
{
int status;
u32 out;
mutex_lock(&dts_update_mutex);
status = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
QRK_DTS_REG_OFFSET_PTPS, &out);
mutex_unlock(&dts_update_mutex);
if (status)
return status;
/*
* Thermal Sensor Programmable Trip Point Register has 8-bit
* fields for critical (catastrophic) and hot set trip point
* thresholds. The threshold value is always offset by its
* temperature base (50 degree Celsius).
*/
*temp = (out >> (trip * QRK_DTS_SHIFT_TP)) & QRK_DTS_MASK_TP_THRES;
*temp -= QRK_DTS_TEMP_BASE;
return 0;
}
static inline int sys_get_trip_temp(struct thermal_zone_device *tzd,
int trip, int *temp)
{
return _get_trip_temp(trip, temp);
}
static inline int sys_get_crit_temp(struct thermal_zone_device *tzd, int *temp)
{
return _get_trip_temp(QRK_DTS_ID_TP_CRITICAL, temp);
}
static int update_trip_temp(struct soc_sensor_entry *aux_entry,
int trip, int temp)
{
u32 out;
u32 temp_out;
u32 store_ptps;
int ret;
mutex_lock(&dts_update_mutex);
if (aux_entry->locked) {
ret = -EPERM;
goto failed;
}
ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
QRK_DTS_REG_OFFSET_PTPS, &store_ptps);
if (ret)
goto failed;
/*
* Protection against unsafe trip point thresdhold value.
* As Quark X1000 data-sheet does not provide any recommendation
* regarding the safe trip point threshold value to use, we choose
* the safe value according to the threshold value set by UEFI BIOS.
*/
if (temp > QRK_DTS_SAFE_TP_THRES)
temp = QRK_DTS_SAFE_TP_THRES;
/*
* Thermal Sensor Programmable Trip Point Register has 8-bit
* fields for critical (catastrophic) and hot set trip point
* thresholds. The threshold value is always offset by its
* temperature base (50 degree Celsius).
*/
temp_out = temp + QRK_DTS_TEMP_BASE;
out = (store_ptps & ~(QRK_DTS_MASK_TP_THRES <<
(trip * QRK_DTS_SHIFT_TP)));
out |= (temp_out & QRK_DTS_MASK_TP_THRES) <<
(trip * QRK_DTS_SHIFT_TP);
ret = iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
QRK_DTS_REG_OFFSET_PTPS, out);
failed:
mutex_unlock(&dts_update_mutex);
return ret;
}
static inline int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
int temp)
{
return update_trip_temp(tzd->devdata, trip, temp);
}
static int sys_get_trip_type(struct thermal_zone_device *thermal,
int trip, enum thermal_trip_type *type)
{
if (trip)
*type = THERMAL_TRIP_HOT;
else
*type = THERMAL_TRIP_CRITICAL;
return 0;
}
static int sys_get_curr_temp(struct thermal_zone_device *tzd,
int *temp)
{
u32 out;
int ret;
mutex_lock(&dts_update_mutex);
ret = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
QRK_DTS_REG_OFFSET_TEMP, &out);
mutex_unlock(&dts_update_mutex);
if (ret)
return ret;
/*
* Thermal Sensor Temperature Register has 8-bit field
* for temperature value (offset by temperature base
* 50 degree Celsius).
*/
out = (out >> QRK_DTS_OFFSET_TEMP) & QRK_DTS_MASK_TEMP;
*temp = out - QRK_DTS_TEMP_BASE;
return 0;
}
static int sys_get_mode(struct thermal_zone_device *tzd,
enum thermal_device_mode *mode)
{
struct soc_sensor_entry *aux_entry = tzd->devdata;
*mode = aux_entry->mode;
return 0;
}
static int sys_set_mode(struct thermal_zone_device *tzd,
enum thermal_device_mode mode)
{
int ret;
mutex_lock(&dts_update_mutex);
if (mode == THERMAL_DEVICE_ENABLED)
ret = soc_dts_enable(tzd);
else
ret = soc_dts_disable(tzd);
mutex_unlock(&dts_update_mutex);
return ret;
}
static struct thermal_zone_device_ops tzone_ops = {
.get_temp = sys_get_curr_temp,
.get_trip_temp = sys_get_trip_temp,
.get_trip_type = sys_get_trip_type,
.set_trip_temp = sys_set_trip_temp,
.get_crit_temp = sys_get_crit_temp,
.get_mode = sys_get_mode,
.set_mode = sys_set_mode,
};
static void free_soc_dts(struct soc_sensor_entry *aux_entry)
{
if (aux_entry) {
if (!aux_entry->locked) {
mutex_lock(&dts_update_mutex);
iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
QRK_DTS_REG_OFFSET_ENABLE,
aux_entry->store_dts_enable);
iosf_mbi_write(QRK_MBI_UNIT_RMU, MBI_REG_WRITE,
QRK_DTS_REG_OFFSET_PTPS,
aux_entry->store_ptps);
mutex_unlock(&dts_update_mutex);
}
thermal_zone_device_unregister(aux_entry->tzone);
kfree(aux_entry);
}
}
static struct soc_sensor_entry *alloc_soc_dts(void)
{
struct soc_sensor_entry *aux_entry;
int err;
u32 out;
int wr_mask;
aux_entry = kzalloc(sizeof(*aux_entry), GFP_KERNEL);
if (!aux_entry) {
err = -ENOMEM;
return ERR_PTR(-ENOMEM);
}
/* Check if DTS register is locked */
err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
QRK_DTS_REG_OFFSET_LOCK, &out);
if (err)
goto err_ret;
if (out & QRK_DTS_LOCK_BIT) {
aux_entry->locked = true;
wr_mask = QRK_DTS_WR_MASK_CLR;
} else {
aux_entry->locked = false;
wr_mask = QRK_DTS_WR_MASK_SET;
}
/* Store DTS default state if DTS registers are not locked */
if (!aux_entry->locked) {
/* Store DTS default enable for restore on exit */
err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
QRK_DTS_REG_OFFSET_ENABLE,
&aux_entry->store_dts_enable);
if (err)
goto err_ret;
/* Store DTS default PTPS register for restore on exit */
err = iosf_mbi_read(QRK_MBI_UNIT_RMU, MBI_REG_READ,
QRK_DTS_REG_OFFSET_PTPS,
&aux_entry->store_ptps);
if (err)
goto err_ret;
}
aux_entry->tzone = thermal_zone_device_register("quark_dts",
QRK_MAX_DTS_TRIPS,
wr_mask,
aux_entry, &tzone_ops, NULL, 0, polling_delay);
if (IS_ERR(aux_entry->tzone)) {
err = PTR_ERR(aux_entry->tzone);
goto err_ret;
}
mutex_lock(&dts_update_mutex);
err = soc_dts_enable(aux_entry->tzone);
mutex_unlock(&dts_update_mutex);
if (err)
goto err_aux_status;
return aux_entry;
err_aux_status:
thermal_zone_device_unregister(aux_entry->tzone);
err_ret:
kfree(aux_entry);
return ERR_PTR(err);
}
static const struct x86_cpu_id qrk_thermal_ids[] __initconst = {
{ X86_VENDOR_INTEL, X86_FAMILY_QUARK, X86_MODEL_QUARK_X1000 },
{}
};
MODULE_DEVICE_TABLE(x86cpu, qrk_thermal_ids);
static int __init intel_quark_thermal_init(void)
{
int err = 0;
if (!x86_match_cpu(qrk_thermal_ids) || !iosf_mbi_available())
return -ENODEV;
soc_dts = alloc_soc_dts();
if (IS_ERR(soc_dts)) {
err = PTR_ERR(soc_dts);
goto err_free;
}
return 0;
err_free:
free_soc_dts(soc_dts);
return err;
}
static void __exit intel_quark_thermal_exit(void)
{
free_soc_dts(soc_dts);
}
module_init(intel_quark_thermal_init)
module_exit(intel_quark_thermal_exit)
MODULE_DESCRIPTION("Intel Quark DTS Thermal Driver");
MODULE_AUTHOR("Ong Boon Leong <boon.leong.ong@intel.com>");
MODULE_LICENSE("Dual BSD/GPL");

View File

@@ -0,0 +1,478 @@
/*
* intel_soc_dts_iosf.c
* Copyright (c) 2015, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <asm/iosf_mbi.h>
#include "intel_soc_dts_iosf.h"
#define SOC_DTS_OFFSET_ENABLE 0xB0
#define SOC_DTS_OFFSET_TEMP 0xB1
#define SOC_DTS_OFFSET_PTPS 0xB2
#define SOC_DTS_OFFSET_PTTS 0xB3
#define SOC_DTS_OFFSET_PTTSS 0xB4
#define SOC_DTS_OFFSET_PTMC 0x80
#define SOC_DTS_TE_AUX0 0xB5
#define SOC_DTS_TE_AUX1 0xB6
#define SOC_DTS_AUX0_ENABLE_BIT BIT(0)
#define SOC_DTS_AUX1_ENABLE_BIT BIT(1)
#define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16)
#define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17)
#define SOC_DTS_TE_SCI_ENABLE BIT(9)
#define SOC_DTS_TE_SMI_ENABLE BIT(10)
#define SOC_DTS_TE_MSI_ENABLE BIT(11)
#define SOC_DTS_TE_APICA_ENABLE BIT(14)
#define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4)
/* DTS encoding for TJ MAX temperature */
#define SOC_DTS_TJMAX_ENCODING 0x7F
/* Only 2 out of 4 is allowed for OSPM */
#define SOC_MAX_DTS_TRIPS 2
/* Mask for two trips in status bits */
#define SOC_DTS_TRIP_MASK 0x03
/* DTS0 and DTS 1 */
#define SOC_MAX_DTS_SENSORS 2
static int get_tj_max(u32 *tj_max)
{
u32 eax, edx;
u32 val;
int err;
err = rdmsr_safe(MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
if (err)
goto err_ret;
else {
val = (eax >> 16) & 0xff;
if (val)
*tj_max = val * 1000;
else {
err = -EINVAL;
goto err_ret;
}
}
return 0;
err_ret:
*tj_max = 0;
return err;
}
static int sys_get_trip_temp(struct thermal_zone_device *tzd, int trip,
int *temp)
{
int status;
u32 out;
struct intel_soc_dts_sensor_entry *dts;
struct intel_soc_dts_sensors *sensors;
dts = tzd->devdata;
sensors = dts->sensors;
mutex_lock(&sensors->dts_update_lock);
status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_PTPS, &out);
mutex_unlock(&sensors->dts_update_lock);
if (status)
return status;
out = (out >> (trip * 8)) & SOC_DTS_TJMAX_ENCODING;
if (!out)
*temp = 0;
else
*temp = sensors->tj_max - out * 1000;
return 0;
}
static int update_trip_temp(struct intel_soc_dts_sensor_entry *dts,
int thres_index, int temp,
enum thermal_trip_type trip_type)
{
int status;
u32 temp_out;
u32 out;
u32 store_ptps;
u32 store_ptmc;
u32 store_te_out;
u32 te_out;
u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE;
struct intel_soc_dts_sensors *sensors = dts->sensors;
if (sensors->intr_type == INTEL_SOC_DTS_INTERRUPT_MSI)
int_enable_bit |= SOC_DTS_TE_MSI_ENABLE;
temp_out = (sensors->tj_max - temp) / 1000;
status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_PTPS, &store_ptps);
if (status)
return status;
out = (store_ptps & ~(0xFF << (thres_index * 8)));
out |= (temp_out & 0xFF) << (thres_index * 8);
status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_PTPS, out);
if (status)
return status;
pr_debug("update_trip_temp PTPS = %x\n", out);
status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_PTMC, &out);
if (status)
goto err_restore_ptps;
store_ptmc = out;
status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_TE_AUX0 + thres_index,
&te_out);
if (status)
goto err_restore_ptmc;
store_te_out = te_out;
/* Enable for CPU module 0 and module 1 */
out |= (SOC_DTS_CPU_MODULE0_ENABLE_BIT |
SOC_DTS_CPU_MODULE1_ENABLE_BIT);
if (temp) {
if (thres_index)
out |= SOC_DTS_AUX1_ENABLE_BIT;
else
out |= SOC_DTS_AUX0_ENABLE_BIT;
te_out |= int_enable_bit;
} else {
if (thres_index)
out &= ~SOC_DTS_AUX1_ENABLE_BIT;
else
out &= ~SOC_DTS_AUX0_ENABLE_BIT;
te_out &= ~int_enable_bit;
}
status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_PTMC, out);
if (status)
goto err_restore_te_out;
status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_TE_AUX0 + thres_index,
te_out);
if (status)
goto err_restore_te_out;
dts->trip_types[thres_index] = trip_type;
return 0;
err_restore_te_out:
iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_PTMC, store_te_out);
err_restore_ptmc:
iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_PTMC, store_ptmc);
err_restore_ptps:
iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_PTPS, store_ptps);
/* Nothing we can do if restore fails */
return status;
}
static int sys_set_trip_temp(struct thermal_zone_device *tzd, int trip,
int temp)
{
struct intel_soc_dts_sensor_entry *dts = tzd->devdata;
struct intel_soc_dts_sensors *sensors = dts->sensors;
int status;
if (temp > sensors->tj_max)
return -EINVAL;
mutex_lock(&sensors->dts_update_lock);
status = update_trip_temp(tzd->devdata, trip, temp,
dts->trip_types[trip]);
mutex_unlock(&sensors->dts_update_lock);
return status;
}
static int sys_get_trip_type(struct thermal_zone_device *tzd,
int trip, enum thermal_trip_type *type)
{
struct intel_soc_dts_sensor_entry *dts;
dts = tzd->devdata;
*type = dts->trip_types[trip];
return 0;
}
static int sys_get_curr_temp(struct thermal_zone_device *tzd,
int *temp)
{
int status;
u32 out;
struct intel_soc_dts_sensor_entry *dts;
struct intel_soc_dts_sensors *sensors;
dts = tzd->devdata;
sensors = dts->sensors;
status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_TEMP, &out);
if (status)
return status;
out = (out & dts->temp_mask) >> dts->temp_shift;
out -= SOC_DTS_TJMAX_ENCODING;
*temp = sensors->tj_max - out * 1000;
return 0;
}
static struct thermal_zone_device_ops tzone_ops = {
.get_temp = sys_get_curr_temp,
.get_trip_temp = sys_get_trip_temp,
.get_trip_type = sys_get_trip_type,
.set_trip_temp = sys_set_trip_temp,
};
static int soc_dts_enable(int id)
{
u32 out;
int ret;
ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_ENABLE, &out);
if (ret)
return ret;
if (!(out & BIT(id))) {
out |= BIT(id);
ret = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_ENABLE, out);
if (ret)
return ret;
}
return ret;
}
static void remove_dts_thermal_zone(struct intel_soc_dts_sensor_entry *dts)
{
if (dts) {
iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_ENABLE, dts->store_status);
thermal_zone_device_unregister(dts->tzone);
}
}
static int add_dts_thermal_zone(int id, struct intel_soc_dts_sensor_entry *dts,
bool notification_support, int trip_cnt,
int read_only_trip_cnt)
{
char name[10];
int trip_count = 0;
int trip_mask = 0;
u32 store_ptps;
int ret;
int i;
/* Store status to restor on exit */
ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_ENABLE, &dts->store_status);
if (ret)
goto err_ret;
dts->id = id;
dts->temp_mask = 0x00FF << (id * 8);
dts->temp_shift = id * 8;
if (notification_support) {
trip_count = min(SOC_MAX_DTS_TRIPS, trip_cnt);
trip_mask = BIT(trip_count - read_only_trip_cnt) - 1;
}
/* Check if the writable trip we provide is not used by BIOS */
ret = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_PTPS, &store_ptps);
if (ret)
trip_mask = 0;
else {
for (i = 0; i < trip_count; ++i) {
if (trip_mask & BIT(i))
if (store_ptps & (0xff << (i * 8)))
trip_mask &= ~BIT(i);
}
}
dts->trip_mask = trip_mask;
dts->trip_count = trip_count;
snprintf(name, sizeof(name), "soc_dts%d", id);
dts->tzone = thermal_zone_device_register(name,
trip_count,
trip_mask,
dts, &tzone_ops,
NULL, 0, 0);
if (IS_ERR(dts->tzone)) {
ret = PTR_ERR(dts->tzone);
goto err_ret;
}
ret = soc_dts_enable(id);
if (ret)
goto err_enable;
return 0;
err_enable:
thermal_zone_device_unregister(dts->tzone);
err_ret:
return ret;
}
int intel_soc_dts_iosf_add_read_only_critical_trip(
struct intel_soc_dts_sensors *sensors, int critical_offset)
{
int i, j;
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
for (j = 0; j < sensors->soc_dts[i].trip_count; ++j) {
if (!(sensors->soc_dts[i].trip_mask & BIT(j))) {
return update_trip_temp(&sensors->soc_dts[i], j,
sensors->tj_max - critical_offset,
THERMAL_TRIP_CRITICAL);
}
}
}
return -EINVAL;
}
EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_add_read_only_critical_trip);
void intel_soc_dts_iosf_interrupt_handler(struct intel_soc_dts_sensors *sensors)
{
u32 sticky_out;
int status;
u32 ptmc_out;
unsigned long flags;
spin_lock_irqsave(&sensors->intr_notify_lock, flags);
status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_PTMC, &ptmc_out);
ptmc_out |= SOC_DTS_PTMC_APIC_DEASSERT_BIT;
status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_PTMC, ptmc_out);
status = iosf_mbi_read(BT_MBI_UNIT_PMC, MBI_REG_READ,
SOC_DTS_OFFSET_PTTSS, &sticky_out);
pr_debug("status %d PTTSS %x\n", status, sticky_out);
if (sticky_out & SOC_DTS_TRIP_MASK) {
int i;
/* reset sticky bit */
status = iosf_mbi_write(BT_MBI_UNIT_PMC, MBI_REG_WRITE,
SOC_DTS_OFFSET_PTTSS, sticky_out);
spin_unlock_irqrestore(&sensors->intr_notify_lock, flags);
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
pr_debug("TZD update for zone %d\n", i);
thermal_zone_device_update(sensors->soc_dts[i].tzone,
THERMAL_EVENT_UNSPECIFIED);
}
} else
spin_unlock_irqrestore(&sensors->intr_notify_lock, flags);
}
EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_interrupt_handler);
struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
enum intel_soc_dts_interrupt_type intr_type, int trip_count,
int read_only_trip_count)
{
struct intel_soc_dts_sensors *sensors;
bool notification;
u32 tj_max;
int ret;
int i;
if (!iosf_mbi_available())
return ERR_PTR(-ENODEV);
if (!trip_count || read_only_trip_count > trip_count)
return ERR_PTR(-EINVAL);
if (get_tj_max(&tj_max))
return ERR_PTR(-EINVAL);
sensors = kzalloc(sizeof(*sensors), GFP_KERNEL);
if (!sensors)
return ERR_PTR(-ENOMEM);
spin_lock_init(&sensors->intr_notify_lock);
mutex_init(&sensors->dts_update_lock);
sensors->intr_type = intr_type;
sensors->tj_max = tj_max;
if (intr_type == INTEL_SOC_DTS_INTERRUPT_NONE)
notification = false;
else
notification = true;
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
sensors->soc_dts[i].sensors = sensors;
ret = add_dts_thermal_zone(i, &sensors->soc_dts[i],
notification, trip_count,
read_only_trip_count);
if (ret)
goto err_free;
}
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
ret = update_trip_temp(&sensors->soc_dts[i], 0, 0,
THERMAL_TRIP_PASSIVE);
if (ret)
goto err_remove_zone;
ret = update_trip_temp(&sensors->soc_dts[i], 1, 0,
THERMAL_TRIP_PASSIVE);
if (ret)
goto err_remove_zone;
}
return sensors;
err_remove_zone:
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i)
remove_dts_thermal_zone(&sensors->soc_dts[i]);
err_free:
kfree(sensors);
return ERR_PTR(ret);
}
EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_init);
void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors)
{
int i;
for (i = 0; i < SOC_MAX_DTS_SENSORS; ++i) {
update_trip_temp(&sensors->soc_dts[i], 0, 0, 0);
update_trip_temp(&sensors->soc_dts[i], 1, 0, 0);
remove_dts_thermal_zone(&sensors->soc_dts[i]);
}
kfree(sensors);
}
EXPORT_SYMBOL_GPL(intel_soc_dts_iosf_exit);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,62 @@
/*
* intel_soc_dts_iosf.h
* Copyright (c) 2015, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#ifndef _INTEL_SOC_DTS_IOSF_CORE_H
#define _INTEL_SOC_DTS_IOSF_CORE_H
#include <linux/thermal.h>
/* DTS0 and DTS 1 */
#define SOC_MAX_DTS_SENSORS 2
enum intel_soc_dts_interrupt_type {
INTEL_SOC_DTS_INTERRUPT_NONE,
INTEL_SOC_DTS_INTERRUPT_APIC,
INTEL_SOC_DTS_INTERRUPT_MSI,
INTEL_SOC_DTS_INTERRUPT_SCI,
INTEL_SOC_DTS_INTERRUPT_SMI,
};
struct intel_soc_dts_sensors;
struct intel_soc_dts_sensor_entry {
int id;
u32 temp_mask;
u32 temp_shift;
u32 store_status;
u32 trip_mask;
u32 trip_count;
enum thermal_trip_type trip_types[2];
struct thermal_zone_device *tzone;
struct intel_soc_dts_sensors *sensors;
};
struct intel_soc_dts_sensors {
u32 tj_max;
spinlock_t intr_notify_lock;
struct mutex dts_update_lock;
enum intel_soc_dts_interrupt_type intr_type;
struct intel_soc_dts_sensor_entry soc_dts[SOC_MAX_DTS_SENSORS];
};
struct intel_soc_dts_sensors *intel_soc_dts_iosf_init(
enum intel_soc_dts_interrupt_type intr_type, int trip_count,
int read_only_trip_count);
void intel_soc_dts_iosf_exit(struct intel_soc_dts_sensors *sensors);
void intel_soc_dts_iosf_interrupt_handler(
struct intel_soc_dts_sensors *sensors);
int intel_soc_dts_iosf_add_read_only_critical_trip(
struct intel_soc_dts_sensors *sensors, int critical_offset);
#endif

View File

@@ -0,0 +1,132 @@
/*
* intel_soc_dts_thermal.c
* Copyright (c) 2014, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/acpi.h>
#include <linux/module.h>
#include <linux/interrupt.h>
#include <asm/cpu_device_id.h>
#include <asm/intel-family.h>
#include "intel_soc_dts_iosf.h"
#define CRITICAL_OFFSET_FROM_TJ_MAX 5000
static int crit_offset = CRITICAL_OFFSET_FROM_TJ_MAX;
module_param(crit_offset, int, 0644);
MODULE_PARM_DESC(crit_offset,
"Critical Temperature offset from tj max in millidegree Celsius.");
/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */
#define BYT_SOC_DTS_APIC_IRQ 86
static int soc_dts_thres_gsi;
static int soc_dts_thres_irq;
static struct intel_soc_dts_sensors *soc_dts;
static irqreturn_t soc_irq_thread_fn(int irq, void *dev_data)
{
pr_debug("proc_thermal_interrupt\n");
intel_soc_dts_iosf_interrupt_handler(soc_dts);
return IRQ_HANDLED;
}
static const struct x86_cpu_id soc_thermal_ids[] = {
{ X86_VENDOR_INTEL, 6, INTEL_FAM6_ATOM_SILVERMONT, 0,
BYT_SOC_DTS_APIC_IRQ},
{}
};
MODULE_DEVICE_TABLE(x86cpu, soc_thermal_ids);
static int __init intel_soc_thermal_init(void)
{
int err = 0;
const struct x86_cpu_id *match_cpu;
match_cpu = x86_match_cpu(soc_thermal_ids);
if (!match_cpu)
return -ENODEV;
/* Create a zone with 2 trips with marked as read only */
soc_dts = intel_soc_dts_iosf_init(INTEL_SOC_DTS_INTERRUPT_APIC, 2, 1);
if (IS_ERR(soc_dts)) {
err = PTR_ERR(soc_dts);
return err;
}
soc_dts_thres_gsi = (int)match_cpu->driver_data;
if (soc_dts_thres_gsi) {
/*
* Note the flags here MUST match the firmware defaults, rather
* then the request_irq flags, otherwise we get an EBUSY error.
*/
soc_dts_thres_irq = acpi_register_gsi(NULL, soc_dts_thres_gsi,
ACPI_LEVEL_SENSITIVE,
ACPI_ACTIVE_LOW);
if (soc_dts_thres_irq < 0) {
pr_warn("intel_soc_dts: Could not get IRQ for GSI %d, err %d\n",
soc_dts_thres_gsi, soc_dts_thres_irq);
soc_dts_thres_irq = 0;
}
}
if (soc_dts_thres_irq) {
err = request_threaded_irq(soc_dts_thres_irq, NULL,
soc_irq_thread_fn,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"soc_dts", soc_dts);
if (err) {
/*
* Do not just error out because the user space thermal
* daemon such as DPTF may use polling instead of being
* interrupt driven.
*/
pr_warn("request_threaded_irq ret %d\n", err);
}
}
err = intel_soc_dts_iosf_add_read_only_critical_trip(soc_dts,
crit_offset);
if (err)
goto error_trips;
return 0;
error_trips:
if (soc_dts_thres_irq) {
free_irq(soc_dts_thres_irq, soc_dts);
acpi_unregister_gsi(soc_dts_thres_gsi);
}
intel_soc_dts_iosf_exit(soc_dts);
return err;
}
static void __exit intel_soc_thermal_exit(void)
{
if (soc_dts_thres_irq) {
free_irq(soc_dts_thres_irq, soc_dts);
acpi_unregister_gsi(soc_dts_thres_gsi);
}
intel_soc_dts_iosf_exit(soc_dts);
}
module_init(intel_soc_thermal_init)
module_exit(intel_soc_thermal_exit)
MODULE_DESCRIPTION("Intel SoC DTS Thermal Driver");
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,558 @@
/*
* x86_pkg_temp_thermal driver
* Copyright (c) 2013, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.
*
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/param.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/cpu.h>
#include <linux/smp.h>
#include <linux/slab.h>
#include <linux/pm.h>
#include <linux/thermal.h>
#include <linux/debugfs.h>
#include <asm/cpu_device_id.h>
#include <asm/mce.h>
/*
* Rate control delay: Idea is to introduce denounce effect
* This should be long enough to avoid reduce events, when
* threshold is set to a temperature, which is constantly
* violated, but at the short enough to take any action.
* The action can be remove threshold or change it to next
* interesting setting. Based on experiments, in around
* every 5 seconds under load will give us a significant
* temperature change.
*/
#define PKG_TEMP_THERMAL_NOTIFY_DELAY 5000
static int notify_delay_ms = PKG_TEMP_THERMAL_NOTIFY_DELAY;
module_param(notify_delay_ms, int, 0644);
MODULE_PARM_DESC(notify_delay_ms,
"User space notification delay in milli seconds.");
/* Number of trip points in thermal zone. Currently it can't
* be more than 2. MSR can allow setting and getting notifications
* for only 2 thresholds. This define enforces this, if there
* is some wrong values returned by cpuid for number of thresholds.
*/
#define MAX_NUMBER_OF_TRIPS 2
struct pkg_device {
int cpu;
bool work_scheduled;
u32 tj_max;
u32 msr_pkg_therm_low;
u32 msr_pkg_therm_high;
struct delayed_work work;
struct thermal_zone_device *tzone;
struct cpumask cpumask;
};
static struct thermal_zone_params pkg_temp_tz_params = {
.no_hwmon = true,
};
/* Keep track of how many package pointers we allocated in init() */
static int max_packages __read_mostly;
/* Array of package pointers */
static struct pkg_device **packages;
/* Serializes interrupt notification, work and hotplug */
static DEFINE_SPINLOCK(pkg_temp_lock);
/* Protects zone operation in the work function against hotplug removal */
static DEFINE_MUTEX(thermal_zone_mutex);
/* The dynamically assigned cpu hotplug state for module_exit() */
static enum cpuhp_state pkg_thermal_hp_state __read_mostly;
/* Debug counters to show using debugfs */
static struct dentry *debugfs;
static unsigned int pkg_interrupt_cnt;
static unsigned int pkg_work_cnt;
static int pkg_temp_debugfs_init(void)
{
struct dentry *d;
debugfs = debugfs_create_dir("pkg_temp_thermal", NULL);
if (!debugfs)
return -ENOENT;
d = debugfs_create_u32("pkg_thres_interrupt", S_IRUGO, debugfs,
&pkg_interrupt_cnt);
if (!d)
goto err_out;
d = debugfs_create_u32("pkg_thres_work", S_IRUGO, debugfs,
&pkg_work_cnt);
if (!d)
goto err_out;
return 0;
err_out:
debugfs_remove_recursive(debugfs);
return -ENOENT;
}
/*
* Protection:
*
* - cpu hotplug: Read serialized by cpu hotplug lock
* Write must hold pkg_temp_lock
*
* - Other callsites: Must hold pkg_temp_lock
*/
static struct pkg_device *pkg_temp_thermal_get_dev(unsigned int cpu)
{
int pkgid = topology_logical_package_id(cpu);
if (pkgid >= 0 && pkgid < max_packages)
return packages[pkgid];
return NULL;
}
/*
* tj-max is is interesting because threshold is set relative to this
* temperature.
*/
static int get_tj_max(int cpu, u32 *tj_max)
{
u32 eax, edx, val;
int err;
err = rdmsr_safe_on_cpu(cpu, MSR_IA32_TEMPERATURE_TARGET, &eax, &edx);
if (err)
return err;
val = (eax >> 16) & 0xff;
*tj_max = val * 1000;
return val ? 0 : -EINVAL;
}
static int sys_get_curr_temp(struct thermal_zone_device *tzd, int *temp)
{
struct pkg_device *pkgdev = tzd->devdata;
u32 eax, edx;
rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_STATUS, &eax, &edx);
if (eax & 0x80000000) {
*temp = pkgdev->tj_max - ((eax >> 16) & 0x7f) * 1000;
pr_debug("sys_get_curr_temp %d\n", *temp);
return 0;
}
return -EINVAL;
}
static int sys_get_trip_temp(struct thermal_zone_device *tzd,
int trip, int *temp)
{
struct pkg_device *pkgdev = tzd->devdata;
unsigned long thres_reg_value;
u32 mask, shift, eax, edx;
int ret;
if (trip >= MAX_NUMBER_OF_TRIPS)
return -EINVAL;
if (trip) {
mask = THERM_MASK_THRESHOLD1;
shift = THERM_SHIFT_THRESHOLD1;
} else {
mask = THERM_MASK_THRESHOLD0;
shift = THERM_SHIFT_THRESHOLD0;
}
ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
&eax, &edx);
if (ret < 0)
return ret;
thres_reg_value = (eax & mask) >> shift;
if (thres_reg_value)
*temp = pkgdev->tj_max - thres_reg_value * 1000;
else
*temp = 0;
pr_debug("sys_get_trip_temp %d\n", *temp);
return 0;
}
static int
sys_set_trip_temp(struct thermal_zone_device *tzd, int trip, int temp)
{
struct pkg_device *pkgdev = tzd->devdata;
u32 l, h, mask, shift, intr;
int ret;
if (trip >= MAX_NUMBER_OF_TRIPS || temp >= pkgdev->tj_max)
return -EINVAL;
ret = rdmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT,
&l, &h);
if (ret < 0)
return ret;
if (trip) {
mask = THERM_MASK_THRESHOLD1;
shift = THERM_SHIFT_THRESHOLD1;
intr = THERM_INT_THRESHOLD1_ENABLE;
} else {
mask = THERM_MASK_THRESHOLD0;
shift = THERM_SHIFT_THRESHOLD0;
intr = THERM_INT_THRESHOLD0_ENABLE;
}
l &= ~mask;
/*
* When users space sets a trip temperature == 0, which is indication
* that, it is no longer interested in receiving notifications.
*/
if (!temp) {
l &= ~intr;
} else {
l |= (pkgdev->tj_max - temp)/1000 << shift;
l |= intr;
}
return wrmsr_on_cpu(pkgdev->cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
}
static int sys_get_trip_type(struct thermal_zone_device *thermal, int trip,
enum thermal_trip_type *type)
{
*type = THERMAL_TRIP_PASSIVE;
return 0;
}
/* Thermal zone callback registry */
static struct thermal_zone_device_ops tzone_ops = {
.get_temp = sys_get_curr_temp,
.get_trip_temp = sys_get_trip_temp,
.get_trip_type = sys_get_trip_type,
.set_trip_temp = sys_set_trip_temp,
};
static bool pkg_thermal_rate_control(void)
{
return true;
}
/* Enable threshold interrupt on local package/cpu */
static inline void enable_pkg_thres_interrupt(void)
{
u8 thres_0, thres_1;
u32 l, h;
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
/* only enable/disable if it had valid threshold value */
thres_0 = (l & THERM_MASK_THRESHOLD0) >> THERM_SHIFT_THRESHOLD0;
thres_1 = (l & THERM_MASK_THRESHOLD1) >> THERM_SHIFT_THRESHOLD1;
if (thres_0)
l |= THERM_INT_THRESHOLD0_ENABLE;
if (thres_1)
l |= THERM_INT_THRESHOLD1_ENABLE;
wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
}
/* Disable threshold interrupt on local package/cpu */
static inline void disable_pkg_thres_interrupt(void)
{
u32 l, h;
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
l &= ~(THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE);
wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, l, h);
}
static void pkg_temp_thermal_threshold_work_fn(struct work_struct *work)
{
struct thermal_zone_device *tzone = NULL;
int cpu = smp_processor_id();
struct pkg_device *pkgdev;
u64 msr_val, wr_val;
mutex_lock(&thermal_zone_mutex);
spin_lock_irq(&pkg_temp_lock);
++pkg_work_cnt;
pkgdev = pkg_temp_thermal_get_dev(cpu);
if (!pkgdev) {
spin_unlock_irq(&pkg_temp_lock);
mutex_unlock(&thermal_zone_mutex);
return;
}
pkgdev->work_scheduled = false;
rdmsrl(MSR_IA32_PACKAGE_THERM_STATUS, msr_val);
wr_val = msr_val & ~(THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1);
if (wr_val != msr_val) {
wrmsrl(MSR_IA32_PACKAGE_THERM_STATUS, wr_val);
tzone = pkgdev->tzone;
}
enable_pkg_thres_interrupt();
spin_unlock_irq(&pkg_temp_lock);
/*
* If tzone is not NULL, then thermal_zone_mutex will prevent the
* concurrent removal in the cpu offline callback.
*/
if (tzone)
thermal_zone_device_update(tzone, THERMAL_EVENT_UNSPECIFIED);
mutex_unlock(&thermal_zone_mutex);
}
static void pkg_thermal_schedule_work(int cpu, struct delayed_work *work)
{
unsigned long ms = msecs_to_jiffies(notify_delay_ms);
schedule_delayed_work_on(cpu, work, ms);
}
static int pkg_thermal_notify(u64 msr_val)
{
int cpu = smp_processor_id();
struct pkg_device *pkgdev;
unsigned long flags;
spin_lock_irqsave(&pkg_temp_lock, flags);
++pkg_interrupt_cnt;
disable_pkg_thres_interrupt();
/* Work is per package, so scheduling it once is enough. */
pkgdev = pkg_temp_thermal_get_dev(cpu);
if (pkgdev && !pkgdev->work_scheduled) {
pkgdev->work_scheduled = true;
pkg_thermal_schedule_work(pkgdev->cpu, &pkgdev->work);
}
spin_unlock_irqrestore(&pkg_temp_lock, flags);
return 0;
}
static int pkg_temp_thermal_device_add(unsigned int cpu)
{
int pkgid = topology_logical_package_id(cpu);
u32 tj_max, eax, ebx, ecx, edx;
struct pkg_device *pkgdev;
int thres_count, err;
if (pkgid >= max_packages)
return -ENOMEM;
cpuid(6, &eax, &ebx, &ecx, &edx);
thres_count = ebx & 0x07;
if (!thres_count)
return -ENODEV;
thres_count = clamp_val(thres_count, 0, MAX_NUMBER_OF_TRIPS);
err = get_tj_max(cpu, &tj_max);
if (err)
return err;
pkgdev = kzalloc(sizeof(*pkgdev), GFP_KERNEL);
if (!pkgdev)
return -ENOMEM;
INIT_DELAYED_WORK(&pkgdev->work, pkg_temp_thermal_threshold_work_fn);
pkgdev->cpu = cpu;
pkgdev->tj_max = tj_max;
pkgdev->tzone = thermal_zone_device_register("x86_pkg_temp",
thres_count,
(thres_count == MAX_NUMBER_OF_TRIPS) ? 0x03 : 0x01,
pkgdev, &tzone_ops, &pkg_temp_tz_params, 0, 0);
if (IS_ERR(pkgdev->tzone)) {
err = PTR_ERR(pkgdev->tzone);
kfree(pkgdev);
return err;
}
/* Store MSR value for package thermal interrupt, to restore at exit */
rdmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT, pkgdev->msr_pkg_therm_low,
pkgdev->msr_pkg_therm_high);
cpumask_set_cpu(cpu, &pkgdev->cpumask);
spin_lock_irq(&pkg_temp_lock);
packages[pkgid] = pkgdev;
spin_unlock_irq(&pkg_temp_lock);
return 0;
}
static int pkg_thermal_cpu_offline(unsigned int cpu)
{
struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu);
bool lastcpu, was_target;
int target;
if (!pkgdev)
return 0;
target = cpumask_any_but(&pkgdev->cpumask, cpu);
cpumask_clear_cpu(cpu, &pkgdev->cpumask);
lastcpu = target >= nr_cpu_ids;
/*
* Remove the sysfs files, if this is the last cpu in the package
* before doing further cleanups.
*/
if (lastcpu) {
struct thermal_zone_device *tzone = pkgdev->tzone;
/*
* We must protect against a work function calling
* thermal_zone_update, after/while unregister. We null out
* the pointer under the zone mutex, so the worker function
* won't try to call.
*/
mutex_lock(&thermal_zone_mutex);
pkgdev->tzone = NULL;
mutex_unlock(&thermal_zone_mutex);
thermal_zone_device_unregister(tzone);
}
/* Protect against work and interrupts */
spin_lock_irq(&pkg_temp_lock);
/*
* Check whether this cpu was the current target and store the new
* one. When we drop the lock, then the interrupt notify function
* will see the new target.
*/
was_target = pkgdev->cpu == cpu;
pkgdev->cpu = target;
/*
* If this is the last CPU in the package remove the package
* reference from the array and restore the interrupt MSR. When we
* drop the lock neither the interrupt notify function nor the
* worker will see the package anymore.
*/
if (lastcpu) {
packages[topology_logical_package_id(cpu)] = NULL;
/* After this point nothing touches the MSR anymore. */
wrmsr(MSR_IA32_PACKAGE_THERM_INTERRUPT,
pkgdev->msr_pkg_therm_low, pkgdev->msr_pkg_therm_high);
}
/*
* Check whether there is work scheduled and whether the work is
* targeted at the outgoing CPU.
*/
if (pkgdev->work_scheduled && was_target) {
/*
* To cancel the work we need to drop the lock, otherwise
* we might deadlock if the work needs to be flushed.
*/
spin_unlock_irq(&pkg_temp_lock);
cancel_delayed_work_sync(&pkgdev->work);
spin_lock_irq(&pkg_temp_lock);
/*
* If this is not the last cpu in the package and the work
* did not run after we dropped the lock above, then we
* need to reschedule the work, otherwise the interrupt
* stays disabled forever.
*/
if (!lastcpu && pkgdev->work_scheduled)
pkg_thermal_schedule_work(target, &pkgdev->work);
}
spin_unlock_irq(&pkg_temp_lock);
/* Final cleanup if this is the last cpu */
if (lastcpu)
kfree(pkgdev);
return 0;
}
static int pkg_thermal_cpu_online(unsigned int cpu)
{
struct pkg_device *pkgdev = pkg_temp_thermal_get_dev(cpu);
struct cpuinfo_x86 *c = &cpu_data(cpu);
/* Paranoia check */
if (!cpu_has(c, X86_FEATURE_DTHERM) || !cpu_has(c, X86_FEATURE_PTS))
return -ENODEV;
/* If the package exists, nothing to do */
if (pkgdev) {
cpumask_set_cpu(cpu, &pkgdev->cpumask);
return 0;
}
return pkg_temp_thermal_device_add(cpu);
}
static const struct x86_cpu_id __initconst pkg_temp_thermal_ids[] = {
{ X86_VENDOR_INTEL, X86_FAMILY_ANY, X86_MODEL_ANY, X86_FEATURE_PTS },
{}
};
MODULE_DEVICE_TABLE(x86cpu, pkg_temp_thermal_ids);
static int __init pkg_temp_thermal_init(void)
{
int ret;
if (!x86_match_cpu(pkg_temp_thermal_ids))
return -ENODEV;
max_packages = topology_max_packages();
packages = kcalloc(max_packages, sizeof(struct pkg_device *),
GFP_KERNEL);
if (!packages)
return -ENOMEM;
ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "thermal/x86_pkg:online",
pkg_thermal_cpu_online, pkg_thermal_cpu_offline);
if (ret < 0)
goto err;
/* Store the state for module exit */
pkg_thermal_hp_state = ret;
platform_thermal_package_notify = pkg_thermal_notify;
platform_thermal_package_rate_control = pkg_thermal_rate_control;
/* Don't care if it fails */
pkg_temp_debugfs_init();
return 0;
err:
kfree(packages);
return ret;
}
module_init(pkg_temp_thermal_init)
static void __exit pkg_temp_thermal_exit(void)
{
platform_thermal_package_notify = NULL;
platform_thermal_package_rate_control = NULL;
cpuhp_remove_state(pkg_thermal_hp_state);
debugfs_remove_recursive(debugfs);
kfree(packages);
}
module_exit(pkg_temp_thermal_exit)
MODULE_DESCRIPTION("X86 PKG TEMP Thermal Driver");
MODULE_AUTHOR("Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com>");
MODULE_LICENSE("GPL v2");