diff options
Diffstat (limited to 'drivers/thermal/intel/int340x_thermal/int3400_thermal.c')
-rw-r--r-- | drivers/thermal/intel/int340x_thermal/int3400_thermal.c | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/drivers/thermal/intel/int340x_thermal/int3400_thermal.c b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c new file mode 100644 index 000000000..28913867c --- /dev/null +++ b/drivers/thermal/intel/int340x_thermal/int3400_thermal.c @@ -0,0 +1,614 @@ +// 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; + u32 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 device_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 device *dev, struct device_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); + kfree(thermal_prop[0]); + kfree(thermal_prop[1]); + kfree(thermal_prop[2]); + kfree(thermal_prop[3]); +} + +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_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; + + if (mode != thermal->mode) + result = int3400_thermal_run_osc(priv->adev->handle, + priv->current_uuid_index, + mode == THERMAL_DEVICE_ENABLED); + + + 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); + if (!priv->data_vault) { + kfree(buffer.pointer); + return; + } + + 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}, + {} +}; + +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"); |