OpenCloudOS-Kernel/drivers/thermal/intel/int340x_thermal/int3400_thermal.c

607 lines
14 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* INT3400 thermal driver
*
* Copyright (C) 2014, Intel Corporation
* Authors: Zhang Rui <rui.zhang@intel.com>
*/
#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
#define INT3400_ODVP_CHANGED 0x88
#define INT3400_KEEP_ALIVE 0xA0
enum int3400_thermal_uuid {
INT3400_THERMAL_PASSIVE_1,
INT3400_THERMAL_ACTIVE,
INT3400_THERMAL_CRITICAL,
INT3400_THERMAL_ADAPTIVE_PERFORMANCE,
INT3400_THERMAL_EMERGENCY_CALL_MODE,
INT3400_THERMAL_PASSIVE_2,
INT3400_THERMAL_POWER_BOSS,
INT3400_THERMAL_VIRTUAL_SENSOR,
INT3400_THERMAL_COOLING_MODE,
INT3400_THERMAL_HARDWARE_DUTY_CYCLING,
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",
"63BE270F-1C11-48FD-A6F7-3AF253FF3E2D",
"5349962F-71E6-431D-9AE8-0A635B710AEE",
"9E04115A-AE87-4D1C-9500-0F3E340BFE75",
"F5A35014-C209-46A4-993A-EB56DE7530A1",
"6ED722A7-9240-48A5-B479-31EEF723D7CF",
"16CAF1B7-DD38-40ED-B1C1-1B8A1913D531",
"BE84BABF-C4D4-403D-B495-3128FD44dAC1",
};
struct odvp_attr;
struct int3400_thermal_priv {
struct acpi_device *adev;
struct platform_device *pdev;
struct thermal_zone_device *thermal;
int art_count;
struct art *arts;
int trt_count;
struct trt *trts;
u8 uuid_bitmap;
int rel_misc_dev_res;
int current_uuid_index;
char *data_vault;
int odvp_count;
int *odvp;
struct odvp_attr *odvp_attrs;
};
static int evaluate_odvp(struct int3400_thermal_priv *priv);
struct odvp_attr {
int odvp;
struct int3400_thermal_priv *priv;
struct kobj_attribute attr;
};
static ssize_t data_vault_read(struct file *file, struct kobject *kobj,
struct bin_attribute *attr, char *buf, loff_t off, size_t count)
{
memcpy(buf, attr->private + off, count);
return count;
}
static BIN_ATTR_RO(data_vault, 0);
static struct bin_attribute *data_attributes[] = {
&bin_attr_data_vault,
NULL,
};
static ssize_t imok_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
acpi_status status;
int input, ret;
ret = kstrtouint(buf, 10, &input);
if (ret)
return ret;
status = acpi_execute_simple_method(priv->adev->handle, "IMOK", input);
if (ACPI_FAILURE(status))
return -EIO;
return count;
}
static DEVICE_ATTR_WO(imok);
static struct attribute *imok_attr[] = {
&dev_attr_imok.attr,
NULL
};
static const struct attribute_group data_attribute_group = {
.bin_attrs = data_attributes,
.attrs = imok_attr,
};
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;
if (!priv->uuid_bitmap)
return sprintf(buf, "UNKNOWN\n");
for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
if (priv->uuid_bitmap & (1 << i))
if (PAGE_SIZE - length > 0)
length += scnprintf(&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->current_uuid_index == -1)
return sprintf(buf, "INVALID\n");
return sprintf(buf, "%s\n",
int3400_thermal_uuids[priv->current_uuid_index]);
}
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 (!strncmp(buf, int3400_thermal_uuids[i],
sizeof(int3400_thermal_uuids[i]) - 1)) {
/*
* If we have a list of supported UUIDs, make sure
* this one is supported.
*/
if (priv->uuid_bitmap &&
!(priv->uuid_bitmap & (1 << i)))
return -EINVAL;
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 = NULL,
.rev = 1,
.cap.length = 8,
};
if (uuid < 0 || uuid >= INT3400_THERMAL_MAXIMUM_UUID)
return -EINVAL;
context.uuid_str = int3400_thermal_uuids[uuid];
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 ssize_t odvp_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
struct odvp_attr *odvp_attr;
odvp_attr = container_of(attr, struct odvp_attr, attr);
return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
}
static void cleanup_odvp(struct int3400_thermal_priv *priv)
{
int i;
if (priv->odvp_attrs) {
for (i = 0; i < priv->odvp_count; i++) {
sysfs_remove_file(&priv->pdev->dev.kobj,
&priv->odvp_attrs[i].attr.attr);
kfree(priv->odvp_attrs[i].attr.attr.name);
}
kfree(priv->odvp_attrs);
}
kfree(priv->odvp);
priv->odvp_count = 0;
}
static int evaluate_odvp(struct int3400_thermal_priv *priv)
{
struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj = NULL;
acpi_status status;
int i, ret;
status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp);
if (ACPI_FAILURE(status)) {
ret = -EINVAL;
goto out_err;
}
obj = odvp.pointer;
if (obj->type != ACPI_TYPE_PACKAGE) {
ret = -EINVAL;
goto out_err;
}
if (priv->odvp == NULL) {
priv->odvp_count = obj->package.count;
priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int),
GFP_KERNEL);
if (!priv->odvp) {
ret = -ENOMEM;
goto out_err;
}
}
if (priv->odvp_attrs == NULL) {
priv->odvp_attrs = kcalloc(priv->odvp_count,
sizeof(struct odvp_attr),
GFP_KERNEL);
if (!priv->odvp_attrs) {
ret = -ENOMEM;
goto out_err;
}
for (i = 0; i < priv->odvp_count; i++) {
struct odvp_attr *odvp = &priv->odvp_attrs[i];
sysfs_attr_init(&odvp->attr.attr);
odvp->priv = priv;
odvp->odvp = i;
odvp->attr.attr.name = kasprintf(GFP_KERNEL,
"odvp%d", i);
if (!odvp->attr.attr.name) {
ret = -ENOMEM;
goto out_err;
}
odvp->attr.attr.mode = 0444;
odvp->attr.show = odvp_show;
odvp->attr.store = NULL;
ret = sysfs_create_file(&priv->pdev->dev.kobj,
&odvp->attr.attr);
if (ret)
goto out_err;
}
}
for (i = 0; i < obj->package.count; i++) {
if (obj->package.elements[i].type == ACPI_TYPE_INTEGER)
priv->odvp[i] = obj->package.elements[i].integer.value;
}
kfree(obj);
return 0;
out_err:
cleanup_odvp(priv);
kfree(obj);
return ret;
}
static void int3400_notify(acpi_handle handle,
u32 event,
void *data)
{
struct int3400_thermal_priv *priv = data;
char *thermal_prop[5];
int therm_event;
if (!priv)
return;
switch (event) {
case INT3400_THERMAL_TABLE_CHANGED:
therm_event = THERMAL_TABLE_CHANGED;
break;
case INT3400_KEEP_ALIVE:
therm_event = THERMAL_EVENT_KEEP_ALIVE;
break;
case INT3400_ODVP_CHANGED:
evaluate_odvp(priv);
therm_event = THERMAL_DEVICE_POWER_CAPABILITY_CHANGED;
break;
default:
/* Ignore unknown notification codes sent to INT3400 device */
return;
}
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", therm_event);
thermal_prop[4] = NULL;
kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, thermal_prop);
}
static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
thermal: consistently use int for temperatures The thermal code uses int, long and unsigned long for temperatures in different places. Using an unsigned type limits the thermal framework to positive temperatures without need. Also several drivers currently will report temperatures near UINT_MAX for temperatures below 0°C. This will probably immediately shut the machine down due to overtemperature if started below 0°C. 'long' is 64bit on several architectures. This is not needed since INT_MAX °mC is above the melting point of all known materials. Consistently use a plain 'int' for temperatures throughout the thermal code and the drivers. This only changes the places in the drivers where the temperature is passed around as pointer, when drivers internally use another type this is not changed. Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de> Acked-by: Geert Uytterhoeven <geert+renesas@glider.be> Reviewed-by: Jean Delvare <jdelvare@suse.de> Reviewed-by: Lukasz Majewski <l.majewski@samsung.com> Reviewed-by: Darren Hart <dvhart@linux.intel.com> Reviewed-by: Heiko Stuebner <heiko@sntech.de> Reviewed-by: Peter Feuerer <peter@piie.net> Cc: Punit Agrawal <punit.agrawal@arm.com> Cc: Zhang Rui <rui.zhang@intel.com> Cc: Eduardo Valentin <edubezval@gmail.com> Cc: linux-pm@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: Jean Delvare <jdelvare@suse.de> Cc: Peter Feuerer <peter@piie.net> Cc: Heiko Stuebner <heiko@sntech.de> Cc: Lukasz Majewski <l.majewski@samsung.com> Cc: Stephen Warren <swarren@wwwdotorg.org> Cc: Thierry Reding <thierry.reding@gmail.com> Cc: linux-acpi@vger.kernel.org Cc: platform-driver-x86@vger.kernel.org Cc: linux-arm-kernel@lists.infradead.org Cc: linux-omap@vger.kernel.org Cc: linux-samsung-soc@vger.kernel.org Cc: Guenter Roeck <linux@roeck-us.net> Cc: Rafael J. Wysocki <rjw@rjwysocki.net> Cc: Maxime Ripard <maxime.ripard@free-electrons.com> Cc: Darren Hart <dvhart@infradead.org> Cc: lm-sensors@lm-sensors.org Signed-off-by: Zhang Rui <rui.zhang@intel.com>
2015-07-24 14:12:54 +08:00
int *temp)
{
*temp = 20 * 1000; /* faked temp sensor with 20C */
return 0;
}
static int int3400_thermal_change_mode(struct thermal_zone_device *thermal,
enum thermal_device_mode mode)
{
struct int3400_thermal_priv *priv = thermal->devdata;
int result = 0;
if (!priv)
return -EINVAL;
thermal: Use mode helpers in drivers Use thermal_zone_device_{en|dis}able() and thermal_zone_device_is_enabled(). Consequently, all set_mode() implementations in drivers: - can stop modifying tzd's "mode" member, - shall stop taking tzd's lock, as it is taken in the helpers - shall stop calling thermal_zone_device_update() as it is called in the helpers - can assume they are called when the mode truly changes, so checks to verify that can be dropped Not providing set_mode() by a driver no longer prevents the core from being able to set tzd's mode, so the relevant check in mode_store() is removed. Other comments: - acpi/thermal.c: tz->thermal_zone->mode will be updated only after we return from set_mode(), so use function parameter in thermal_set_mode() instead, no need to call acpi_thermal_check() in set_mode() - thermal/imx_thermal.c: regmap writes and mode assignment are done in thermal_zone_device_{en|dis}able() and set_mode() callback - thermal/intel/intel_quark_dts_thermal.c: soc_dts_{en|dis}able() are a part of set_mode() callback, so they don't need to modify tzd->mode, and don't need to fall back to the opposite mode if unsuccessful, as the return value will be propagated to thermal_zone_device_{en|dis}able() and ultimately tzd's member will not be changed in thermal_zone_device_set_mode(). - thermal/of-thermal.c: no need to set zone->mode to DISABLED in of_parse_thermal_zones() as a tzd is kzalloc'ed so mode is DISABLED anyway Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@collabora.com> [for acerhdf] Acked-by: Peter Kaestle <peter@piie.net> Reviewed-by: Amit Kucheria <amit.kucheria@linaro.org> Reviewed-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> Link: https://lore.kernel.org/r/20200629122925.21729-8-andrzej.p@collabora.com
2020-06-29 20:29:21 +08:00
if (mode != thermal->mode)
result = int3400_thermal_run_osc(priv->adev->handle,
priv->current_uuid_index,
mode == THERMAL_DEVICE_ENABLED);
thermal: Use mode helpers in drivers Use thermal_zone_device_{en|dis}able() and thermal_zone_device_is_enabled(). Consequently, all set_mode() implementations in drivers: - can stop modifying tzd's "mode" member, - shall stop taking tzd's lock, as it is taken in the helpers - shall stop calling thermal_zone_device_update() as it is called in the helpers - can assume they are called when the mode truly changes, so checks to verify that can be dropped Not providing set_mode() by a driver no longer prevents the core from being able to set tzd's mode, so the relevant check in mode_store() is removed. Other comments: - acpi/thermal.c: tz->thermal_zone->mode will be updated only after we return from set_mode(), so use function parameter in thermal_set_mode() instead, no need to call acpi_thermal_check() in set_mode() - thermal/imx_thermal.c: regmap writes and mode assignment are done in thermal_zone_device_{en|dis}able() and set_mode() callback - thermal/intel/intel_quark_dts_thermal.c: soc_dts_{en|dis}able() are a part of set_mode() callback, so they don't need to modify tzd->mode, and don't need to fall back to the opposite mode if unsuccessful, as the return value will be propagated to thermal_zone_device_{en|dis}able() and ultimately tzd's member will not be changed in thermal_zone_device_set_mode(). - thermal/of-thermal.c: no need to set zone->mode to DISABLED in of_parse_thermal_zones() as a tzd is kzalloc'ed so mode is DISABLED anyway Signed-off-by: Andrzej Pietrasiewicz <andrzej.p@collabora.com> [for acerhdf] Acked-by: Peter Kaestle <peter@piie.net> Reviewed-by: Amit Kucheria <amit.kucheria@linaro.org> Reviewed-by: Bartlomiej Zolnierkiewicz <b.zolnierkie@samsung.com> Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> Link: https://lore.kernel.org/r/20200629122925.21729-8-andrzej.p@collabora.com
2020-06-29 20:29:21 +08:00
evaluate_odvp(priv);
return result;
}
static struct thermal_zone_device_ops int3400_thermal_ops = {
.get_temp = int3400_thermal_get_temp,
.change_mode = int3400_thermal_change_mode,
};
static struct thermal_zone_params int3400_thermal_params = {
.governor_name = "user_space",
.no_hwmon = true,
};
static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
union acpi_object *obj;
acpi_status status;
status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL,
&buffer);
if (ACPI_FAILURE(status) || !buffer.length)
return;
obj = buffer.pointer;
if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
|| obj->package.elements[0].type != ACPI_TYPE_BUFFER) {
kfree(buffer.pointer);
return;
}
priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
obj->package.elements[0].buffer.length,
GFP_KERNEL);
bin_attr_data_vault.private = priv->data_vault;
bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
kfree(buffer.pointer);
}
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->pdev = pdev;
priv->adev = adev;
result = int3400_thermal_get_uuids(priv);
/* Missing IDSP isn't fatal */
if (result && result != -ENODEV)
goto free_priv;
priv->current_uuid_index = -1;
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);
int3400_setup_gddv(priv);
evaluate_odvp(priv);
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;
if (priv->data_vault) {
result = sysfs_create_group(&pdev->dev.kobj,
&data_attribute_group);
if (result)
goto free_uuid;
}
result = acpi_install_notify_handler(
priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
(void *)priv);
if (result)
goto free_sysfs;
return 0;
free_sysfs:
cleanup_odvp(priv);
if (priv->data_vault) {
sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
kfree(priv->data_vault);
}
free_uuid:
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);
cleanup_odvp(priv);
if (!priv->rel_misc_dev_res)
acpi_thermal_rel_misc_device_remove(priv->adev->handle);
if (priv->data_vault)
sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
thermal_zone_device_unregister(priv->thermal);
kfree(priv->data_vault);
kfree(priv->trts);
kfree(priv->arts);
kfree(priv);
return 0;
}
static const struct acpi_device_id int3400_thermal_match[] = {
{"INT3400", 0},
{"INTC1040", 0},
{"INTC1041", 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");