1d2912cb1SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 23e8c4d31SAmit Kucheria /* 33e8c4d31SAmit Kucheria * INT3400 thermal driver 43e8c4d31SAmit Kucheria * 53e8c4d31SAmit Kucheria * Copyright (C) 2014, Intel Corporation 63e8c4d31SAmit Kucheria * Authors: Zhang Rui <rui.zhang@intel.com> 73e8c4d31SAmit Kucheria */ 83e8c4d31SAmit Kucheria 93e8c4d31SAmit Kucheria #include <linux/module.h> 103e8c4d31SAmit Kucheria #include <linux/platform_device.h> 113e8c4d31SAmit Kucheria #include <linux/acpi.h> 123e8c4d31SAmit Kucheria #include <linux/thermal.h> 133e8c4d31SAmit Kucheria #include "acpi_thermal_rel.h" 143e8c4d31SAmit Kucheria 153e8c4d31SAmit Kucheria #define INT3400_THERMAL_TABLE_CHANGED 0x83 16006f006fSMatthew Garrett #define INT3400_ODVP_CHANGED 0x88 1794a3c35eSSrinivas Pandruvada #define INT3400_KEEP_ALIVE 0xA0 183e8c4d31SAmit Kucheria 193e8c4d31SAmit Kucheria enum int3400_thermal_uuid { 20c7ff2976SSrinivas Pandruvada INT3400_THERMAL_ACTIVE = 0, 213e8c4d31SAmit Kucheria INT3400_THERMAL_PASSIVE_1, 223e8c4d31SAmit Kucheria INT3400_THERMAL_CRITICAL, 2316fc8ecaSMatthew Garrett INT3400_THERMAL_ADAPTIVE_PERFORMANCE, 2416fc8ecaSMatthew Garrett INT3400_THERMAL_EMERGENCY_CALL_MODE, 2516fc8ecaSMatthew Garrett INT3400_THERMAL_PASSIVE_2, 2616fc8ecaSMatthew Garrett INT3400_THERMAL_POWER_BOSS, 2716fc8ecaSMatthew Garrett INT3400_THERMAL_VIRTUAL_SENSOR, 2816fc8ecaSMatthew Garrett INT3400_THERMAL_COOLING_MODE, 2916fc8ecaSMatthew Garrett INT3400_THERMAL_HARDWARE_DUTY_CYCLING, 303e8c4d31SAmit Kucheria INT3400_THERMAL_MAXIMUM_UUID, 313e8c4d31SAmit Kucheria }; 323e8c4d31SAmit Kucheria 333e8c4d31SAmit Kucheria static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = { 343e8c4d31SAmit Kucheria "3A95C389-E4B8-4629-A526-C52C88626BAE", 35c7ff2976SSrinivas Pandruvada "42A441D6-AE6A-462b-A84B-4A8CE79027D3", 363e8c4d31SAmit Kucheria "97C68AE7-15FA-499c-B8C9-5DA81D606E0A", 3716fc8ecaSMatthew Garrett "63BE270F-1C11-48FD-A6F7-3AF253FF3E2D", 3816fc8ecaSMatthew Garrett "5349962F-71E6-431D-9AE8-0A635B710AEE", 3916fc8ecaSMatthew Garrett "9E04115A-AE87-4D1C-9500-0F3E340BFE75", 4016fc8ecaSMatthew Garrett "F5A35014-C209-46A4-993A-EB56DE7530A1", 4116fc8ecaSMatthew Garrett "6ED722A7-9240-48A5-B479-31EEF723D7CF", 4216fc8ecaSMatthew Garrett "16CAF1B7-DD38-40ED-B1C1-1B8A1913D531", 4316fc8ecaSMatthew Garrett "BE84BABF-C4D4-403D-B495-3128FD44dAC1", 443e8c4d31SAmit Kucheria }; 453e8c4d31SAmit Kucheria 46006f006fSMatthew Garrett struct odvp_attr; 47006f006fSMatthew Garrett 483e8c4d31SAmit Kucheria struct int3400_thermal_priv { 493e8c4d31SAmit Kucheria struct acpi_device *adev; 50006f006fSMatthew Garrett struct platform_device *pdev; 513e8c4d31SAmit Kucheria struct thermal_zone_device *thermal; 523e8c4d31SAmit Kucheria int art_count; 533e8c4d31SAmit Kucheria struct art *arts; 543e8c4d31SAmit Kucheria int trt_count; 553e8c4d31SAmit Kucheria struct trt *trts; 56668f69a5SSrinivas Pandruvada u32 uuid_bitmap; 573e8c4d31SAmit Kucheria int rel_misc_dev_res; 583e8c4d31SAmit Kucheria int current_uuid_index; 590ba13c76SMatthew Garrett char *data_vault; 60006f006fSMatthew Garrett int odvp_count; 61006f006fSMatthew Garrett int *odvp; 62c7ff2976SSrinivas Pandruvada u32 os_uuid_mask; 63006f006fSMatthew Garrett struct odvp_attr *odvp_attrs; 64006f006fSMatthew Garrett }; 65006f006fSMatthew Garrett 66006f006fSMatthew Garrett static int evaluate_odvp(struct int3400_thermal_priv *priv); 67006f006fSMatthew Garrett 68006f006fSMatthew Garrett struct odvp_attr { 69006f006fSMatthew Garrett int odvp; 70006f006fSMatthew Garrett struct int3400_thermal_priv *priv; 71006f006fSMatthew Garrett struct kobj_attribute attr; 720ba13c76SMatthew Garrett }; 730ba13c76SMatthew Garrett 740ba13c76SMatthew Garrett static ssize_t data_vault_read(struct file *file, struct kobject *kobj, 750ba13c76SMatthew Garrett struct bin_attribute *attr, char *buf, loff_t off, size_t count) 760ba13c76SMatthew Garrett { 770ba13c76SMatthew Garrett memcpy(buf, attr->private + off, count); 780ba13c76SMatthew Garrett return count; 790ba13c76SMatthew Garrett } 800ba13c76SMatthew Garrett 810ba13c76SMatthew Garrett static BIN_ATTR_RO(data_vault, 0); 820ba13c76SMatthew Garrett 830ba13c76SMatthew Garrett static struct bin_attribute *data_attributes[] = { 840ba13c76SMatthew Garrett &bin_attr_data_vault, 850ba13c76SMatthew Garrett NULL, 860ba13c76SMatthew Garrett }; 870ba13c76SMatthew Garrett 8894a3c35eSSrinivas Pandruvada static ssize_t imok_store(struct device *dev, struct device_attribute *attr, 8994a3c35eSSrinivas Pandruvada const char *buf, size_t count) 9094a3c35eSSrinivas Pandruvada { 9194a3c35eSSrinivas Pandruvada struct int3400_thermal_priv *priv = dev_get_drvdata(dev); 9294a3c35eSSrinivas Pandruvada acpi_status status; 9394a3c35eSSrinivas Pandruvada int input, ret; 9494a3c35eSSrinivas Pandruvada 9594a3c35eSSrinivas Pandruvada ret = kstrtouint(buf, 10, &input); 9694a3c35eSSrinivas Pandruvada if (ret) 9794a3c35eSSrinivas Pandruvada return ret; 9894a3c35eSSrinivas Pandruvada status = acpi_execute_simple_method(priv->adev->handle, "IMOK", input); 9994a3c35eSSrinivas Pandruvada if (ACPI_FAILURE(status)) 10094a3c35eSSrinivas Pandruvada return -EIO; 10194a3c35eSSrinivas Pandruvada 10294a3c35eSSrinivas Pandruvada return count; 10394a3c35eSSrinivas Pandruvada } 10494a3c35eSSrinivas Pandruvada 10594a3c35eSSrinivas Pandruvada static DEVICE_ATTR_WO(imok); 10694a3c35eSSrinivas Pandruvada 10794a3c35eSSrinivas Pandruvada static struct attribute *imok_attr[] = { 10894a3c35eSSrinivas Pandruvada &dev_attr_imok.attr, 10994a3c35eSSrinivas Pandruvada NULL 11094a3c35eSSrinivas Pandruvada }; 11194a3c35eSSrinivas Pandruvada 112f1b07a14SSumeet Pawnikar static const struct attribute_group imok_attribute_group = { 113f1b07a14SSumeet Pawnikar .attrs = imok_attr, 114f1b07a14SSumeet Pawnikar }; 115f1b07a14SSumeet Pawnikar 1160ba13c76SMatthew Garrett static const struct attribute_group data_attribute_group = { 1170ba13c76SMatthew Garrett .bin_attrs = data_attributes, 1183e8c4d31SAmit Kucheria }; 1193e8c4d31SAmit Kucheria 1203e8c4d31SAmit Kucheria static ssize_t available_uuids_show(struct device *dev, 1213e8c4d31SAmit Kucheria struct device_attribute *attr, 1223e8c4d31SAmit Kucheria char *buf) 1233e8c4d31SAmit Kucheria { 1243e8c4d31SAmit Kucheria struct int3400_thermal_priv *priv = dev_get_drvdata(dev); 1253e8c4d31SAmit Kucheria int i; 1263e8c4d31SAmit Kucheria int length = 0; 1273e8c4d31SAmit Kucheria 1288d485da0SMatthew Garrett if (!priv->uuid_bitmap) 1298d485da0SMatthew Garrett return sprintf(buf, "UNKNOWN\n"); 1308d485da0SMatthew Garrett 1313e8c4d31SAmit Kucheria for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) { 1323e8c4d31SAmit Kucheria if (priv->uuid_bitmap & (1 << i)) 133f21431f2STakashi Iwai length += scnprintf(&buf[length], 1343e8c4d31SAmit Kucheria PAGE_SIZE - length, 1353e8c4d31SAmit Kucheria "%s\n", 1363e8c4d31SAmit Kucheria int3400_thermal_uuids[i]); 1373e8c4d31SAmit Kucheria } 1383e8c4d31SAmit Kucheria 1393e8c4d31SAmit Kucheria return length; 1403e8c4d31SAmit Kucheria } 1413e8c4d31SAmit Kucheria 1423e8c4d31SAmit Kucheria static ssize_t current_uuid_show(struct device *dev, 1433e8c4d31SAmit Kucheria struct device_attribute *devattr, char *buf) 1443e8c4d31SAmit Kucheria { 1453e8c4d31SAmit Kucheria struct int3400_thermal_priv *priv = dev_get_drvdata(dev); 146c7ff2976SSrinivas Pandruvada int i, length = 0; 1473e8c4d31SAmit Kucheria 148c7ff2976SSrinivas Pandruvada if (priv->current_uuid_index > 0) 1493e8c4d31SAmit Kucheria return sprintf(buf, "%s\n", 1503e8c4d31SAmit Kucheria int3400_thermal_uuids[priv->current_uuid_index]); 151c7ff2976SSrinivas Pandruvada 152c7ff2976SSrinivas Pandruvada for (i = 0; i <= INT3400_THERMAL_CRITICAL; i++) { 153c7ff2976SSrinivas Pandruvada if (priv->os_uuid_mask & BIT(i)) 154c7ff2976SSrinivas Pandruvada length += scnprintf(&buf[length], 155c7ff2976SSrinivas Pandruvada PAGE_SIZE - length, 156c7ff2976SSrinivas Pandruvada "%s\n", 157c7ff2976SSrinivas Pandruvada int3400_thermal_uuids[i]); 158c7ff2976SSrinivas Pandruvada } 159c7ff2976SSrinivas Pandruvada 160c7ff2976SSrinivas Pandruvada if (length) 161c7ff2976SSrinivas Pandruvada return length; 162c7ff2976SSrinivas Pandruvada 163c7ff2976SSrinivas Pandruvada return sprintf(buf, "INVALID\n"); 164c7ff2976SSrinivas Pandruvada } 165c7ff2976SSrinivas Pandruvada 166c7ff2976SSrinivas Pandruvada static int int3400_thermal_run_osc(acpi_handle handle, char *uuid_str, int *enable) 167c7ff2976SSrinivas Pandruvada { 168c7ff2976SSrinivas Pandruvada u32 ret, buf[2]; 169c7ff2976SSrinivas Pandruvada acpi_status status; 170c7ff2976SSrinivas Pandruvada int result = 0; 171c7ff2976SSrinivas Pandruvada struct acpi_osc_context context = { 172c7ff2976SSrinivas Pandruvada .uuid_str = NULL, 173c7ff2976SSrinivas Pandruvada .rev = 1, 174c7ff2976SSrinivas Pandruvada .cap.length = 8, 175c7ff2976SSrinivas Pandruvada }; 176c7ff2976SSrinivas Pandruvada 177c7ff2976SSrinivas Pandruvada context.uuid_str = uuid_str; 178c7ff2976SSrinivas Pandruvada 179c7ff2976SSrinivas Pandruvada buf[OSC_QUERY_DWORD] = 0; 180c7ff2976SSrinivas Pandruvada buf[OSC_SUPPORT_DWORD] = *enable; 181c7ff2976SSrinivas Pandruvada 182c7ff2976SSrinivas Pandruvada context.cap.pointer = buf; 183c7ff2976SSrinivas Pandruvada 184c7ff2976SSrinivas Pandruvada status = acpi_run_osc(handle, &context); 185c7ff2976SSrinivas Pandruvada if (ACPI_SUCCESS(status)) { 186c7ff2976SSrinivas Pandruvada ret = *((u32 *)(context.ret.pointer + 4)); 187c7ff2976SSrinivas Pandruvada if (ret != *enable) 188c7ff2976SSrinivas Pandruvada result = -EPERM; 189c7ff2976SSrinivas Pandruvada 190c7ff2976SSrinivas Pandruvada kfree(context.ret.pointer); 191bdff938dSDavidlohr Bueso } else 192bdff938dSDavidlohr Bueso result = -EPERM; 193c7ff2976SSrinivas Pandruvada 194c7ff2976SSrinivas Pandruvada return result; 1953e8c4d31SAmit Kucheria } 1963e8c4d31SAmit Kucheria 1973e8c4d31SAmit Kucheria static ssize_t current_uuid_store(struct device *dev, 1983e8c4d31SAmit Kucheria struct device_attribute *attr, 1993e8c4d31SAmit Kucheria const char *buf, size_t count) 2003e8c4d31SAmit Kucheria { 2013e8c4d31SAmit Kucheria struct int3400_thermal_priv *priv = dev_get_drvdata(dev); 2023e8c4d31SAmit Kucheria int i; 2033e8c4d31SAmit Kucheria 2043e8c4d31SAmit Kucheria for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) { 2058d485da0SMatthew Garrett if (!strncmp(buf, int3400_thermal_uuids[i], 2068d485da0SMatthew Garrett sizeof(int3400_thermal_uuids[i]) - 1)) { 2078d485da0SMatthew Garrett /* 2088d485da0SMatthew Garrett * If we have a list of supported UUIDs, make sure 2098d485da0SMatthew Garrett * this one is supported. 2108d485da0SMatthew Garrett */ 211c7ff2976SSrinivas Pandruvada if (priv->uuid_bitmap & BIT(i)) { 2123e8c4d31SAmit Kucheria priv->current_uuid_index = i; 2133e8c4d31SAmit Kucheria return count; 2143e8c4d31SAmit Kucheria } 215c7ff2976SSrinivas Pandruvada 216c7ff2976SSrinivas Pandruvada /* 217c7ff2976SSrinivas Pandruvada * There is support of only 3 policies via the new 218c7ff2976SSrinivas Pandruvada * _OSC to inform OS capability: 219c7ff2976SSrinivas Pandruvada * INT3400_THERMAL_ACTIVE 220c7ff2976SSrinivas Pandruvada * INT3400_THERMAL_PASSIVE_1 221c7ff2976SSrinivas Pandruvada * INT3400_THERMAL_CRITICAL 222c7ff2976SSrinivas Pandruvada */ 223c7ff2976SSrinivas Pandruvada 224c7ff2976SSrinivas Pandruvada if (i > INT3400_THERMAL_CRITICAL) 225c7ff2976SSrinivas Pandruvada return -EINVAL; 226c7ff2976SSrinivas Pandruvada 227c7ff2976SSrinivas Pandruvada priv->os_uuid_mask |= BIT(i); 228c7ff2976SSrinivas Pandruvada 229c7ff2976SSrinivas Pandruvada break; 230c7ff2976SSrinivas Pandruvada } 2313e8c4d31SAmit Kucheria } 2323e8c4d31SAmit Kucheria 233c7ff2976SSrinivas Pandruvada if (priv->os_uuid_mask) { 234c7ff2976SSrinivas Pandruvada int cap, ret; 235c7ff2976SSrinivas Pandruvada 236c7ff2976SSrinivas Pandruvada /* 237c7ff2976SSrinivas Pandruvada * Capability bits: 238c7ff2976SSrinivas Pandruvada * Bit 0: set to 1 to indicate DPTF is active 239c7ff2976SSrinivas Pandruvada * Bi1 1: set to 1 to active cooling is supported by user space daemon 240c7ff2976SSrinivas Pandruvada * Bit 2: set to 1 to passive cooling is supported by user space daemon 241c7ff2976SSrinivas Pandruvada * Bit 3: set to 1 to critical trip is handled by user space daemon 242c7ff2976SSrinivas Pandruvada */ 243c7ff2976SSrinivas Pandruvada cap = ((priv->os_uuid_mask << 1) | 0x01); 244c7ff2976SSrinivas Pandruvada ret = int3400_thermal_run_osc(priv->adev->handle, 245c7ff2976SSrinivas Pandruvada "b23ba85d-c8b7-3542-88de-8de2ffcfd698", 246c7ff2976SSrinivas Pandruvada &cap); 247c7ff2976SSrinivas Pandruvada if (ret) 248c7ff2976SSrinivas Pandruvada return ret; 249c7ff2976SSrinivas Pandruvada } 250c7ff2976SSrinivas Pandruvada 251c7ff2976SSrinivas Pandruvada return count; 2523e8c4d31SAmit Kucheria } 2533e8c4d31SAmit Kucheria 2543e8c4d31SAmit Kucheria static DEVICE_ATTR_RW(current_uuid); 2553e8c4d31SAmit Kucheria static DEVICE_ATTR_RO(available_uuids); 2563e8c4d31SAmit Kucheria static struct attribute *uuid_attrs[] = { 2573e8c4d31SAmit Kucheria &dev_attr_available_uuids.attr, 2583e8c4d31SAmit Kucheria &dev_attr_current_uuid.attr, 2593e8c4d31SAmit Kucheria NULL 2603e8c4d31SAmit Kucheria }; 2613e8c4d31SAmit Kucheria 2623e8c4d31SAmit Kucheria static const struct attribute_group uuid_attribute_group = { 2633e8c4d31SAmit Kucheria .attrs = uuid_attrs, 2643e8c4d31SAmit Kucheria .name = "uuids" 2653e8c4d31SAmit Kucheria }; 2663e8c4d31SAmit Kucheria 2673e8c4d31SAmit Kucheria static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv) 2683e8c4d31SAmit Kucheria { 2693e8c4d31SAmit Kucheria struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL}; 2703e8c4d31SAmit Kucheria union acpi_object *obja, *objb; 2713e8c4d31SAmit Kucheria int i, j; 2723e8c4d31SAmit Kucheria int result = 0; 2733e8c4d31SAmit Kucheria acpi_status status; 2743e8c4d31SAmit Kucheria 2753e8c4d31SAmit Kucheria status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf); 2763e8c4d31SAmit Kucheria if (ACPI_FAILURE(status)) 2773e8c4d31SAmit Kucheria return -ENODEV; 2783e8c4d31SAmit Kucheria 2793e8c4d31SAmit Kucheria obja = (union acpi_object *)buf.pointer; 2803e8c4d31SAmit Kucheria if (obja->type != ACPI_TYPE_PACKAGE) { 2813e8c4d31SAmit Kucheria result = -EINVAL; 2823e8c4d31SAmit Kucheria goto end; 2833e8c4d31SAmit Kucheria } 2843e8c4d31SAmit Kucheria 2853e8c4d31SAmit Kucheria for (i = 0; i < obja->package.count; i++) { 2863e8c4d31SAmit Kucheria objb = &obja->package.elements[i]; 2873e8c4d31SAmit Kucheria if (objb->type != ACPI_TYPE_BUFFER) { 2883e8c4d31SAmit Kucheria result = -EINVAL; 2893e8c4d31SAmit Kucheria goto end; 2903e8c4d31SAmit Kucheria } 2913e8c4d31SAmit Kucheria 2923e8c4d31SAmit Kucheria /* UUID must be 16 bytes */ 2933e8c4d31SAmit Kucheria if (objb->buffer.length != 16) { 2943e8c4d31SAmit Kucheria result = -EINVAL; 2953e8c4d31SAmit Kucheria goto end; 2963e8c4d31SAmit Kucheria } 2973e8c4d31SAmit Kucheria 2983e8c4d31SAmit Kucheria for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) { 2993e8c4d31SAmit Kucheria guid_t guid; 3003e8c4d31SAmit Kucheria 3013e8c4d31SAmit Kucheria guid_parse(int3400_thermal_uuids[j], &guid); 3023e8c4d31SAmit Kucheria if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) { 3033e8c4d31SAmit Kucheria priv->uuid_bitmap |= (1 << j); 3043e8c4d31SAmit Kucheria break; 3053e8c4d31SAmit Kucheria } 3063e8c4d31SAmit Kucheria } 3073e8c4d31SAmit Kucheria } 3083e8c4d31SAmit Kucheria 3093e8c4d31SAmit Kucheria end: 3103e8c4d31SAmit Kucheria kfree(buf.pointer); 3113e8c4d31SAmit Kucheria return result; 3123e8c4d31SAmit Kucheria } 3133e8c4d31SAmit Kucheria 314006f006fSMatthew Garrett static ssize_t odvp_show(struct kobject *kobj, struct kobj_attribute *attr, 315006f006fSMatthew Garrett char *buf) 316006f006fSMatthew Garrett { 317006f006fSMatthew Garrett struct odvp_attr *odvp_attr; 318006f006fSMatthew Garrett 319006f006fSMatthew Garrett odvp_attr = container_of(attr, struct odvp_attr, attr); 320006f006fSMatthew Garrett 321006f006fSMatthew Garrett return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]); 322006f006fSMatthew Garrett } 323006f006fSMatthew Garrett 324006f006fSMatthew Garrett static void cleanup_odvp(struct int3400_thermal_priv *priv) 325006f006fSMatthew Garrett { 326006f006fSMatthew Garrett int i; 327006f006fSMatthew Garrett 328006f006fSMatthew Garrett if (priv->odvp_attrs) { 329006f006fSMatthew Garrett for (i = 0; i < priv->odvp_count; i++) { 330006f006fSMatthew Garrett sysfs_remove_file(&priv->pdev->dev.kobj, 331006f006fSMatthew Garrett &priv->odvp_attrs[i].attr.attr); 332006f006fSMatthew Garrett kfree(priv->odvp_attrs[i].attr.attr.name); 333006f006fSMatthew Garrett } 334006f006fSMatthew Garrett kfree(priv->odvp_attrs); 335006f006fSMatthew Garrett } 336006f006fSMatthew Garrett kfree(priv->odvp); 337006f006fSMatthew Garrett priv->odvp_count = 0; 338006f006fSMatthew Garrett } 339006f006fSMatthew Garrett 340006f006fSMatthew Garrett static int evaluate_odvp(struct int3400_thermal_priv *priv) 341006f006fSMatthew Garrett { 342006f006fSMatthew Garrett struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL }; 343006f006fSMatthew Garrett union acpi_object *obj = NULL; 344006f006fSMatthew Garrett acpi_status status; 345006f006fSMatthew Garrett int i, ret; 346006f006fSMatthew Garrett 347006f006fSMatthew Garrett status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp); 348006f006fSMatthew Garrett if (ACPI_FAILURE(status)) { 349006f006fSMatthew Garrett ret = -EINVAL; 350006f006fSMatthew Garrett goto out_err; 351006f006fSMatthew Garrett } 352006f006fSMatthew Garrett 353006f006fSMatthew Garrett obj = odvp.pointer; 354006f006fSMatthew Garrett if (obj->type != ACPI_TYPE_PACKAGE) { 355006f006fSMatthew Garrett ret = -EINVAL; 356006f006fSMatthew Garrett goto out_err; 357006f006fSMatthew Garrett } 358006f006fSMatthew Garrett 359006f006fSMatthew Garrett if (priv->odvp == NULL) { 360006f006fSMatthew Garrett priv->odvp_count = obj->package.count; 361006f006fSMatthew Garrett priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int), 362006f006fSMatthew Garrett GFP_KERNEL); 363006f006fSMatthew Garrett if (!priv->odvp) { 364006f006fSMatthew Garrett ret = -ENOMEM; 365006f006fSMatthew Garrett goto out_err; 366006f006fSMatthew Garrett } 367006f006fSMatthew Garrett } 368006f006fSMatthew Garrett 369006f006fSMatthew Garrett if (priv->odvp_attrs == NULL) { 370006f006fSMatthew Garrett priv->odvp_attrs = kcalloc(priv->odvp_count, 371006f006fSMatthew Garrett sizeof(struct odvp_attr), 372006f006fSMatthew Garrett GFP_KERNEL); 373006f006fSMatthew Garrett if (!priv->odvp_attrs) { 374006f006fSMatthew Garrett ret = -ENOMEM; 375006f006fSMatthew Garrett goto out_err; 376006f006fSMatthew Garrett } 377006f006fSMatthew Garrett for (i = 0; i < priv->odvp_count; i++) { 378006f006fSMatthew Garrett struct odvp_attr *odvp = &priv->odvp_attrs[i]; 379006f006fSMatthew Garrett 380006f006fSMatthew Garrett sysfs_attr_init(&odvp->attr.attr); 381006f006fSMatthew Garrett odvp->priv = priv; 382006f006fSMatthew Garrett odvp->odvp = i; 383006f006fSMatthew Garrett odvp->attr.attr.name = kasprintf(GFP_KERNEL, 384006f006fSMatthew Garrett "odvp%d", i); 385006f006fSMatthew Garrett 386006f006fSMatthew Garrett if (!odvp->attr.attr.name) { 387006f006fSMatthew Garrett ret = -ENOMEM; 388006f006fSMatthew Garrett goto out_err; 389006f006fSMatthew Garrett } 390006f006fSMatthew Garrett odvp->attr.attr.mode = 0444; 391006f006fSMatthew Garrett odvp->attr.show = odvp_show; 392006f006fSMatthew Garrett odvp->attr.store = NULL; 393006f006fSMatthew Garrett ret = sysfs_create_file(&priv->pdev->dev.kobj, 394006f006fSMatthew Garrett &odvp->attr.attr); 395006f006fSMatthew Garrett if (ret) 396006f006fSMatthew Garrett goto out_err; 397006f006fSMatthew Garrett } 398006f006fSMatthew Garrett } 399006f006fSMatthew Garrett 400006f006fSMatthew Garrett for (i = 0; i < obj->package.count; i++) { 401006f006fSMatthew Garrett if (obj->package.elements[i].type == ACPI_TYPE_INTEGER) 402006f006fSMatthew Garrett priv->odvp[i] = obj->package.elements[i].integer.value; 403006f006fSMatthew Garrett } 404006f006fSMatthew Garrett 405006f006fSMatthew Garrett kfree(obj); 406006f006fSMatthew Garrett return 0; 407006f006fSMatthew Garrett 408006f006fSMatthew Garrett out_err: 409006f006fSMatthew Garrett cleanup_odvp(priv); 410006f006fSMatthew Garrett kfree(obj); 411006f006fSMatthew Garrett return ret; 412006f006fSMatthew Garrett } 413006f006fSMatthew Garrett 4143e8c4d31SAmit Kucheria static void int3400_notify(acpi_handle handle, 4153e8c4d31SAmit Kucheria u32 event, 4163e8c4d31SAmit Kucheria void *data) 4173e8c4d31SAmit Kucheria { 4183e8c4d31SAmit Kucheria struct int3400_thermal_priv *priv = data; 4193e8c4d31SAmit Kucheria char *thermal_prop[5]; 42039558030SSrinivas Pandruvada int therm_event; 4213e8c4d31SAmit Kucheria 4223e8c4d31SAmit Kucheria if (!priv) 4233e8c4d31SAmit Kucheria return; 4243e8c4d31SAmit Kucheria 4253e8c4d31SAmit Kucheria switch (event) { 4263e8c4d31SAmit Kucheria case INT3400_THERMAL_TABLE_CHANGED: 42739558030SSrinivas Pandruvada therm_event = THERMAL_TABLE_CHANGED; 4283e8c4d31SAmit Kucheria break; 42994a3c35eSSrinivas Pandruvada case INT3400_KEEP_ALIVE: 43094a3c35eSSrinivas Pandruvada therm_event = THERMAL_EVENT_KEEP_ALIVE; 43194a3c35eSSrinivas Pandruvada break; 432006f006fSMatthew Garrett case INT3400_ODVP_CHANGED: 433006f006fSMatthew Garrett evaluate_odvp(priv); 43439558030SSrinivas Pandruvada therm_event = THERMAL_DEVICE_POWER_CAPABILITY_CHANGED; 435006f006fSMatthew Garrett break; 4363e8c4d31SAmit Kucheria default: 4373e8c4d31SAmit Kucheria /* Ignore unknown notification codes sent to INT3400 device */ 43839558030SSrinivas Pandruvada return; 4393e8c4d31SAmit Kucheria } 44039558030SSrinivas Pandruvada 44139558030SSrinivas Pandruvada thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", priv->thermal->type); 44239558030SSrinivas Pandruvada thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", priv->thermal->temperature); 44339558030SSrinivas Pandruvada thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP="); 44439558030SSrinivas Pandruvada thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", therm_event); 44539558030SSrinivas Pandruvada thermal_prop[4] = NULL; 44639558030SSrinivas Pandruvada kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, thermal_prop); 4473abea10eSChuansheng Liu kfree(thermal_prop[0]); 4483abea10eSChuansheng Liu kfree(thermal_prop[1]); 4493abea10eSChuansheng Liu kfree(thermal_prop[2]); 4503abea10eSChuansheng Liu kfree(thermal_prop[3]); 4513e8c4d31SAmit Kucheria } 4523e8c4d31SAmit Kucheria 4533e8c4d31SAmit Kucheria static int int3400_thermal_get_temp(struct thermal_zone_device *thermal, 4543e8c4d31SAmit Kucheria int *temp) 4553e8c4d31SAmit Kucheria { 4563e8c4d31SAmit Kucheria *temp = 20 * 1000; /* faked temp sensor with 20C */ 4573e8c4d31SAmit Kucheria return 0; 4583e8c4d31SAmit Kucheria } 4593e8c4d31SAmit Kucheria 460f5e50bf4SAndrzej Pietrasiewicz static int int3400_thermal_change_mode(struct thermal_zone_device *thermal, 4613e8c4d31SAmit Kucheria enum thermal_device_mode mode) 4623e8c4d31SAmit Kucheria { 4633e8c4d31SAmit Kucheria struct int3400_thermal_priv *priv = thermal->devdata; 4643e8c4d31SAmit Kucheria int result = 0; 4653e8c4d31SAmit Kucheria 4663e8c4d31SAmit Kucheria if (!priv) 4673e8c4d31SAmit Kucheria return -EINVAL; 4683e8c4d31SAmit Kucheria 469c7ff2976SSrinivas Pandruvada if (mode != thermal->mode) { 470c7ff2976SSrinivas Pandruvada int enabled; 471c7ff2976SSrinivas Pandruvada 472c7ff2976SSrinivas Pandruvada if (priv->current_uuid_index < 0 || 473c7ff2976SSrinivas Pandruvada priv->current_uuid_index >= INT3400_THERMAL_MAXIMUM_UUID) 474c7ff2976SSrinivas Pandruvada return -EINVAL; 475c7ff2976SSrinivas Pandruvada 476c7ff2976SSrinivas Pandruvada enabled = (mode == THERMAL_DEVICE_ENABLED); 4773e8c4d31SAmit Kucheria result = int3400_thermal_run_osc(priv->adev->handle, 478c7ff2976SSrinivas Pandruvada int3400_thermal_uuids[priv->current_uuid_index], 479c7ff2976SSrinivas Pandruvada &enabled); 480c7ff2976SSrinivas Pandruvada } 4817f4957beSAndrzej Pietrasiewicz 482006f006fSMatthew Garrett 483006f006fSMatthew Garrett evaluate_odvp(priv); 484006f006fSMatthew Garrett 4853e8c4d31SAmit Kucheria return result; 4863e8c4d31SAmit Kucheria } 4873e8c4d31SAmit Kucheria 4883e8c4d31SAmit Kucheria static struct thermal_zone_device_ops int3400_thermal_ops = { 4893e8c4d31SAmit Kucheria .get_temp = int3400_thermal_get_temp, 490f5e50bf4SAndrzej Pietrasiewicz .change_mode = int3400_thermal_change_mode, 4913e8c4d31SAmit Kucheria }; 4923e8c4d31SAmit Kucheria 4933e8c4d31SAmit Kucheria static struct thermal_zone_params int3400_thermal_params = { 4943e8c4d31SAmit Kucheria .governor_name = "user_space", 4953e8c4d31SAmit Kucheria .no_hwmon = true, 4963e8c4d31SAmit Kucheria }; 4973e8c4d31SAmit Kucheria 4980ba13c76SMatthew Garrett static void int3400_setup_gddv(struct int3400_thermal_priv *priv) 4990ba13c76SMatthew Garrett { 5000ba13c76SMatthew Garrett struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; 5010ba13c76SMatthew Garrett union acpi_object *obj; 5020ba13c76SMatthew Garrett acpi_status status; 5030ba13c76SMatthew Garrett 5040ba13c76SMatthew Garrett status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL, 5050ba13c76SMatthew Garrett &buffer); 5060ba13c76SMatthew Garrett if (ACPI_FAILURE(status) || !buffer.length) 5070ba13c76SMatthew Garrett return; 5080ba13c76SMatthew Garrett 5090ba13c76SMatthew Garrett obj = buffer.pointer; 5100ba13c76SMatthew Garrett if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1 511*9e5d3d6bSDavidlohr Bueso || obj->package.elements[0].type != ACPI_TYPE_BUFFER) 512*9e5d3d6bSDavidlohr Bueso goto out_free; 5130ba13c76SMatthew Garrett 5140ba13c76SMatthew Garrett priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer, 5150ba13c76SMatthew Garrett obj->package.elements[0].buffer.length, 5160ba13c76SMatthew Garrett GFP_KERNEL); 517*9e5d3d6bSDavidlohr Bueso if (!priv->data_vault) 518*9e5d3d6bSDavidlohr Bueso goto out_free; 51938b16d6cSJiasheng Jiang 5200ba13c76SMatthew Garrett bin_attr_data_vault.private = priv->data_vault; 5210ba13c76SMatthew Garrett bin_attr_data_vault.size = obj->package.elements[0].buffer.length; 522*9e5d3d6bSDavidlohr Bueso out_free: 5230ba13c76SMatthew Garrett kfree(buffer.pointer); 5240ba13c76SMatthew Garrett } 5250ba13c76SMatthew Garrett 5263e8c4d31SAmit Kucheria static int int3400_thermal_probe(struct platform_device *pdev) 5273e8c4d31SAmit Kucheria { 5283e8c4d31SAmit Kucheria struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); 5293e8c4d31SAmit Kucheria struct int3400_thermal_priv *priv; 5303e8c4d31SAmit Kucheria int result; 5313e8c4d31SAmit Kucheria 5323e8c4d31SAmit Kucheria if (!adev) 5333e8c4d31SAmit Kucheria return -ENODEV; 5343e8c4d31SAmit Kucheria 5353e8c4d31SAmit Kucheria priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL); 5363e8c4d31SAmit Kucheria if (!priv) 5373e8c4d31SAmit Kucheria return -ENOMEM; 5383e8c4d31SAmit Kucheria 539006f006fSMatthew Garrett priv->pdev = pdev; 5403e8c4d31SAmit Kucheria priv->adev = adev; 5413e8c4d31SAmit Kucheria 5423e8c4d31SAmit Kucheria result = int3400_thermal_get_uuids(priv); 5438d485da0SMatthew Garrett 5448d485da0SMatthew Garrett /* Missing IDSP isn't fatal */ 5458d485da0SMatthew Garrett if (result && result != -ENODEV) 5463e8c4d31SAmit Kucheria goto free_priv; 5473e8c4d31SAmit Kucheria 5488d485da0SMatthew Garrett priv->current_uuid_index = -1; 5498d485da0SMatthew Garrett 5503e8c4d31SAmit Kucheria result = acpi_parse_art(priv->adev->handle, &priv->art_count, 5513e8c4d31SAmit Kucheria &priv->arts, true); 5523e8c4d31SAmit Kucheria if (result) 5533e8c4d31SAmit Kucheria dev_dbg(&pdev->dev, "_ART table parsing error\n"); 5543e8c4d31SAmit Kucheria 5553e8c4d31SAmit Kucheria result = acpi_parse_trt(priv->adev->handle, &priv->trt_count, 5563e8c4d31SAmit Kucheria &priv->trts, true); 5573e8c4d31SAmit Kucheria if (result) 5583e8c4d31SAmit Kucheria dev_dbg(&pdev->dev, "_TRT table parsing error\n"); 5593e8c4d31SAmit Kucheria 5603e8c4d31SAmit Kucheria platform_set_drvdata(pdev, priv); 5613e8c4d31SAmit Kucheria 5620ba13c76SMatthew Garrett int3400_setup_gddv(priv); 5630ba13c76SMatthew Garrett 564006f006fSMatthew Garrett evaluate_odvp(priv); 565006f006fSMatthew Garrett 5663e8c4d31SAmit Kucheria priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0, 5673e8c4d31SAmit Kucheria priv, &int3400_thermal_ops, 5683e8c4d31SAmit Kucheria &int3400_thermal_params, 0, 0); 5693e8c4d31SAmit Kucheria if (IS_ERR(priv->thermal)) { 5703e8c4d31SAmit Kucheria result = PTR_ERR(priv->thermal); 5713e8c4d31SAmit Kucheria goto free_art_trt; 5723e8c4d31SAmit Kucheria } 5733e8c4d31SAmit Kucheria 5743e8c4d31SAmit Kucheria priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add( 5753e8c4d31SAmit Kucheria priv->adev->handle); 5763e8c4d31SAmit Kucheria 5773e8c4d31SAmit Kucheria result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group); 5783e8c4d31SAmit Kucheria if (result) 5793e8c4d31SAmit Kucheria goto free_rel_misc; 5803e8c4d31SAmit Kucheria 581f1b07a14SSumeet Pawnikar if (acpi_has_method(priv->adev->handle, "IMOK")) { 582f1b07a14SSumeet Pawnikar result = sysfs_create_group(&pdev->dev.kobj, &imok_attribute_group); 583f1b07a14SSumeet Pawnikar if (result) 584f1b07a14SSumeet Pawnikar goto free_imok; 585f1b07a14SSumeet Pawnikar } 586f1b07a14SSumeet Pawnikar 5870ba13c76SMatthew Garrett if (priv->data_vault) { 5880ba13c76SMatthew Garrett result = sysfs_create_group(&pdev->dev.kobj, 5890ba13c76SMatthew Garrett &data_attribute_group); 5900ba13c76SMatthew Garrett if (result) 5910ba13c76SMatthew Garrett goto free_uuid; 5920ba13c76SMatthew Garrett } 5930ba13c76SMatthew Garrett 5943e8c4d31SAmit Kucheria result = acpi_install_notify_handler( 5953e8c4d31SAmit Kucheria priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify, 5963e8c4d31SAmit Kucheria (void *)priv); 5973e8c4d31SAmit Kucheria if (result) 5983e8c4d31SAmit Kucheria goto free_sysfs; 5993e8c4d31SAmit Kucheria 6003e8c4d31SAmit Kucheria return 0; 6013e8c4d31SAmit Kucheria 6023e8c4d31SAmit Kucheria free_sysfs: 603006f006fSMatthew Garrett cleanup_odvp(priv); 604006f006fSMatthew Garrett if (priv->data_vault) { 6050ba13c76SMatthew Garrett sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group); 606006f006fSMatthew Garrett kfree(priv->data_vault); 607006f006fSMatthew Garrett } 6080ba13c76SMatthew Garrett free_uuid: 6093e8c4d31SAmit Kucheria sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); 610f1b07a14SSumeet Pawnikar free_imok: 611f1b07a14SSumeet Pawnikar sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group); 6123e8c4d31SAmit Kucheria free_rel_misc: 6133e8c4d31SAmit Kucheria if (!priv->rel_misc_dev_res) 6143e8c4d31SAmit Kucheria acpi_thermal_rel_misc_device_remove(priv->adev->handle); 6153e8c4d31SAmit Kucheria thermal_zone_device_unregister(priv->thermal); 6163e8c4d31SAmit Kucheria free_art_trt: 6173e8c4d31SAmit Kucheria kfree(priv->trts); 6183e8c4d31SAmit Kucheria kfree(priv->arts); 6193e8c4d31SAmit Kucheria free_priv: 6203e8c4d31SAmit Kucheria kfree(priv); 6213e8c4d31SAmit Kucheria return result; 6223e8c4d31SAmit Kucheria } 6233e8c4d31SAmit Kucheria 6243e8c4d31SAmit Kucheria static int int3400_thermal_remove(struct platform_device *pdev) 6253e8c4d31SAmit Kucheria { 6263e8c4d31SAmit Kucheria struct int3400_thermal_priv *priv = platform_get_drvdata(pdev); 6273e8c4d31SAmit Kucheria 6283e8c4d31SAmit Kucheria acpi_remove_notify_handler( 6293e8c4d31SAmit Kucheria priv->adev->handle, ACPI_DEVICE_NOTIFY, 6303e8c4d31SAmit Kucheria int3400_notify); 6313e8c4d31SAmit Kucheria 632006f006fSMatthew Garrett cleanup_odvp(priv); 633006f006fSMatthew Garrett 6343e8c4d31SAmit Kucheria if (!priv->rel_misc_dev_res) 6353e8c4d31SAmit Kucheria acpi_thermal_rel_misc_device_remove(priv->adev->handle); 6363e8c4d31SAmit Kucheria 6370ba13c76SMatthew Garrett if (priv->data_vault) 6380ba13c76SMatthew Garrett sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group); 6393e8c4d31SAmit Kucheria sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group); 640f1b07a14SSumeet Pawnikar sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group); 6413e8c4d31SAmit Kucheria thermal_zone_device_unregister(priv->thermal); 6420ba13c76SMatthew Garrett kfree(priv->data_vault); 6433e8c4d31SAmit Kucheria kfree(priv->trts); 6443e8c4d31SAmit Kucheria kfree(priv->arts); 6453e8c4d31SAmit Kucheria kfree(priv); 6463e8c4d31SAmit Kucheria return 0; 6473e8c4d31SAmit Kucheria } 6483e8c4d31SAmit Kucheria 6493e8c4d31SAmit Kucheria static const struct acpi_device_id int3400_thermal_match[] = { 6503e8c4d31SAmit Kucheria {"INT3400", 0}, 65126d8bec1SGayatri Kammela {"INTC1040", 0}, 65267698880SSrinivas Pandruvada {"INTC1041", 0}, 653a95be874SSrinivas Pandruvada {"INTC10A0", 0}, 6543e8c4d31SAmit Kucheria {} 6553e8c4d31SAmit Kucheria }; 6563e8c4d31SAmit Kucheria 6573e8c4d31SAmit Kucheria MODULE_DEVICE_TABLE(acpi, int3400_thermal_match); 6583e8c4d31SAmit Kucheria 6593e8c4d31SAmit Kucheria static struct platform_driver int3400_thermal_driver = { 6603e8c4d31SAmit Kucheria .probe = int3400_thermal_probe, 6613e8c4d31SAmit Kucheria .remove = int3400_thermal_remove, 6623e8c4d31SAmit Kucheria .driver = { 6633e8c4d31SAmit Kucheria .name = "int3400 thermal", 6643e8c4d31SAmit Kucheria .acpi_match_table = ACPI_PTR(int3400_thermal_match), 6653e8c4d31SAmit Kucheria }, 6663e8c4d31SAmit Kucheria }; 6673e8c4d31SAmit Kucheria 6683e8c4d31SAmit Kucheria module_platform_driver(int3400_thermal_driver); 6693e8c4d31SAmit Kucheria 6703e8c4d31SAmit Kucheria MODULE_DESCRIPTION("INT3400 Thermal driver"); 6713e8c4d31SAmit Kucheria MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>"); 6723e8c4d31SAmit Kucheria MODULE_LICENSE("GPL"); 673