linux/drivers/thermal/intel/int340x_thermal/int3400_thermal.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * INT3400 thermal driver
   4 *
   5 * Copyright (C) 2014, Intel Corporation
   6 * Authors: Zhang Rui <rui.zhang@intel.com>
   7 */
   8
   9#include <linux/module.h>
  10#include <linux/platform_device.h>
  11#include <linux/acpi.h>
  12#include <linux/thermal.h>
  13#include "acpi_thermal_rel.h"
  14
  15#define INT3400_THERMAL_TABLE_CHANGED 0x83
  16#define INT3400_ODVP_CHANGED 0x88
  17#define INT3400_KEEP_ALIVE 0xA0
  18
  19enum int3400_thermal_uuid {
  20        INT3400_THERMAL_PASSIVE_1,
  21        INT3400_THERMAL_ACTIVE,
  22        INT3400_THERMAL_CRITICAL,
  23        INT3400_THERMAL_ADAPTIVE_PERFORMANCE,
  24        INT3400_THERMAL_EMERGENCY_CALL_MODE,
  25        INT3400_THERMAL_PASSIVE_2,
  26        INT3400_THERMAL_POWER_BOSS,
  27        INT3400_THERMAL_VIRTUAL_SENSOR,
  28        INT3400_THERMAL_COOLING_MODE,
  29        INT3400_THERMAL_HARDWARE_DUTY_CYCLING,
  30        INT3400_THERMAL_MAXIMUM_UUID,
  31};
  32
  33static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
  34        "42A441D6-AE6A-462b-A84B-4A8CE79027D3",
  35        "3A95C389-E4B8-4629-A526-C52C88626BAE",
  36        "97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
  37        "63BE270F-1C11-48FD-A6F7-3AF253FF3E2D",
  38        "5349962F-71E6-431D-9AE8-0A635B710AEE",
  39        "9E04115A-AE87-4D1C-9500-0F3E340BFE75",
  40        "F5A35014-C209-46A4-993A-EB56DE7530A1",
  41        "6ED722A7-9240-48A5-B479-31EEF723D7CF",
  42        "16CAF1B7-DD38-40ED-B1C1-1B8A1913D531",
  43        "BE84BABF-C4D4-403D-B495-3128FD44dAC1",
  44};
  45
  46struct odvp_attr;
  47
  48struct int3400_thermal_priv {
  49        struct acpi_device *adev;
  50        struct platform_device *pdev;
  51        struct thermal_zone_device *thermal;
  52        int art_count;
  53        struct art *arts;
  54        int trt_count;
  55        struct trt *trts;
  56        u8 uuid_bitmap;
  57        int rel_misc_dev_res;
  58        int current_uuid_index;
  59        char *data_vault;
  60        int odvp_count;
  61        int *odvp;
  62        struct odvp_attr *odvp_attrs;
  63};
  64
  65static int evaluate_odvp(struct int3400_thermal_priv *priv);
  66
  67struct odvp_attr {
  68        int odvp;
  69        struct int3400_thermal_priv *priv;
  70        struct kobj_attribute attr;
  71};
  72
  73static ssize_t data_vault_read(struct file *file, struct kobject *kobj,
  74             struct bin_attribute *attr, char *buf, loff_t off, size_t count)
  75{
  76        memcpy(buf, attr->private + off, count);
  77        return count;
  78}
  79
  80static BIN_ATTR_RO(data_vault, 0);
  81
  82static struct bin_attribute *data_attributes[] = {
  83        &bin_attr_data_vault,
  84        NULL,
  85};
  86
  87static ssize_t imok_store(struct device *dev, struct device_attribute *attr,
  88                          const char *buf, size_t count)
  89{
  90        struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
  91        acpi_status status;
  92        int input, ret;
  93
  94        ret = kstrtouint(buf, 10, &input);
  95        if (ret)
  96                return ret;
  97        status = acpi_execute_simple_method(priv->adev->handle, "IMOK", input);
  98        if (ACPI_FAILURE(status))
  99                return -EIO;
 100
 101        return count;
 102}
 103
 104static DEVICE_ATTR_WO(imok);
 105
 106static struct attribute *imok_attr[] = {
 107        &dev_attr_imok.attr,
 108        NULL
 109};
 110
 111static const struct attribute_group imok_attribute_group = {
 112        .attrs = imok_attr,
 113};
 114
 115static const struct attribute_group data_attribute_group = {
 116        .bin_attrs = data_attributes,
 117};
 118
 119static ssize_t available_uuids_show(struct device *dev,
 120                                    struct device_attribute *attr,
 121                                    char *buf)
 122{
 123        struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
 124        int i;
 125        int length = 0;
 126
 127        if (!priv->uuid_bitmap)
 128                return sprintf(buf, "UNKNOWN\n");
 129
 130        for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
 131                if (priv->uuid_bitmap & (1 << i))
 132                        if (PAGE_SIZE - length > 0)
 133                                length += scnprintf(&buf[length],
 134                                                   PAGE_SIZE - length,
 135                                                   "%s\n",
 136                                                   int3400_thermal_uuids[i]);
 137        }
 138
 139        return length;
 140}
 141
 142static ssize_t current_uuid_show(struct device *dev,
 143                                 struct device_attribute *devattr, char *buf)
 144{
 145        struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
 146
 147        if (priv->current_uuid_index == -1)
 148                return sprintf(buf, "INVALID\n");
 149
 150        return sprintf(buf, "%s\n",
 151                       int3400_thermal_uuids[priv->current_uuid_index]);
 152}
 153
 154static ssize_t current_uuid_store(struct device *dev,
 155                                  struct device_attribute *attr,
 156                                  const char *buf, size_t count)
 157{
 158        struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
 159        int i;
 160
 161        for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
 162                if (!strncmp(buf, int3400_thermal_uuids[i],
 163                             sizeof(int3400_thermal_uuids[i]) - 1)) {
 164                        /*
 165                         * If we have a list of supported UUIDs, make sure
 166                         * this one is supported.
 167                         */
 168                        if (priv->uuid_bitmap &&
 169                            !(priv->uuid_bitmap & (1 << i)))
 170                                return -EINVAL;
 171
 172                        priv->current_uuid_index = i;
 173                        return count;
 174                }
 175        }
 176
 177        return -EINVAL;
 178}
 179
 180static DEVICE_ATTR_RW(current_uuid);
 181static DEVICE_ATTR_RO(available_uuids);
 182static struct attribute *uuid_attrs[] = {
 183        &dev_attr_available_uuids.attr,
 184        &dev_attr_current_uuid.attr,
 185        NULL
 186};
 187
 188static const struct attribute_group uuid_attribute_group = {
 189        .attrs = uuid_attrs,
 190        .name = "uuids"
 191};
 192
 193static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)
 194{
 195        struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL};
 196        union acpi_object *obja, *objb;
 197        int i, j;
 198        int result = 0;
 199        acpi_status status;
 200
 201        status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf);
 202        if (ACPI_FAILURE(status))
 203                return -ENODEV;
 204
 205        obja = (union acpi_object *)buf.pointer;
 206        if (obja->type != ACPI_TYPE_PACKAGE) {
 207                result = -EINVAL;
 208                goto end;
 209        }
 210
 211        for (i = 0; i < obja->package.count; i++) {
 212                objb = &obja->package.elements[i];
 213                if (objb->type != ACPI_TYPE_BUFFER) {
 214                        result = -EINVAL;
 215                        goto end;
 216                }
 217
 218                /* UUID must be 16 bytes */
 219                if (objb->buffer.length != 16) {
 220                        result = -EINVAL;
 221                        goto end;
 222                }
 223
 224                for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) {
 225                        guid_t guid;
 226
 227                        guid_parse(int3400_thermal_uuids[j], &guid);
 228                        if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) {
 229                                priv->uuid_bitmap |= (1 << j);
 230                                break;
 231                        }
 232                }
 233        }
 234
 235end:
 236        kfree(buf.pointer);
 237        return result;
 238}
 239
 240static int int3400_thermal_run_osc(acpi_handle handle,
 241                                enum int3400_thermal_uuid uuid, bool enable)
 242{
 243        u32 ret, buf[2];
 244        acpi_status status;
 245        int result = 0;
 246        struct acpi_osc_context context = {
 247                .uuid_str = NULL,
 248                .rev = 1,
 249                .cap.length = 8,
 250        };
 251
 252        if (uuid < 0 || uuid >= INT3400_THERMAL_MAXIMUM_UUID)
 253                return -EINVAL;
 254
 255        context.uuid_str = int3400_thermal_uuids[uuid];
 256
 257        buf[OSC_QUERY_DWORD] = 0;
 258        buf[OSC_SUPPORT_DWORD] = enable;
 259
 260        context.cap.pointer = buf;
 261
 262        status = acpi_run_osc(handle, &context);
 263        if (ACPI_SUCCESS(status)) {
 264                ret = *((u32 *)(context.ret.pointer + 4));
 265                if (ret != enable)
 266                        result = -EPERM;
 267        } else
 268                result = -EPERM;
 269
 270        kfree(context.ret.pointer);
 271
 272        return result;
 273}
 274
 275static ssize_t odvp_show(struct kobject *kobj, struct kobj_attribute *attr,
 276                         char *buf)
 277{
 278        struct odvp_attr *odvp_attr;
 279
 280        odvp_attr = container_of(attr, struct odvp_attr, attr);
 281
 282        return sprintf(buf, "%d\n", odvp_attr->priv->odvp[odvp_attr->odvp]);
 283}
 284
 285static void cleanup_odvp(struct int3400_thermal_priv *priv)
 286{
 287        int i;
 288
 289        if (priv->odvp_attrs) {
 290                for (i = 0; i < priv->odvp_count; i++) {
 291                        sysfs_remove_file(&priv->pdev->dev.kobj,
 292                                          &priv->odvp_attrs[i].attr.attr);
 293                        kfree(priv->odvp_attrs[i].attr.attr.name);
 294                }
 295                kfree(priv->odvp_attrs);
 296        }
 297        kfree(priv->odvp);
 298        priv->odvp_count = 0;
 299}
 300
 301static int evaluate_odvp(struct int3400_thermal_priv *priv)
 302{
 303        struct acpi_buffer odvp = { ACPI_ALLOCATE_BUFFER, NULL };
 304        union acpi_object *obj = NULL;
 305        acpi_status status;
 306        int i, ret;
 307
 308        status = acpi_evaluate_object(priv->adev->handle, "ODVP", NULL, &odvp);
 309        if (ACPI_FAILURE(status)) {
 310                ret = -EINVAL;
 311                goto out_err;
 312        }
 313
 314        obj = odvp.pointer;
 315        if (obj->type != ACPI_TYPE_PACKAGE) {
 316                ret = -EINVAL;
 317                goto out_err;
 318        }
 319
 320        if (priv->odvp == NULL) {
 321                priv->odvp_count = obj->package.count;
 322                priv->odvp = kmalloc_array(priv->odvp_count, sizeof(int),
 323                                     GFP_KERNEL);
 324                if (!priv->odvp) {
 325                        ret = -ENOMEM;
 326                        goto out_err;
 327                }
 328        }
 329
 330        if (priv->odvp_attrs == NULL) {
 331                priv->odvp_attrs = kcalloc(priv->odvp_count,
 332                                           sizeof(struct odvp_attr),
 333                                           GFP_KERNEL);
 334                if (!priv->odvp_attrs) {
 335                        ret = -ENOMEM;
 336                        goto out_err;
 337                }
 338                for (i = 0; i < priv->odvp_count; i++) {
 339                        struct odvp_attr *odvp = &priv->odvp_attrs[i];
 340
 341                        sysfs_attr_init(&odvp->attr.attr);
 342                        odvp->priv = priv;
 343                        odvp->odvp = i;
 344                        odvp->attr.attr.name = kasprintf(GFP_KERNEL,
 345                                                         "odvp%d", i);
 346
 347                        if (!odvp->attr.attr.name) {
 348                                ret = -ENOMEM;
 349                                goto out_err;
 350                        }
 351                        odvp->attr.attr.mode = 0444;
 352                        odvp->attr.show = odvp_show;
 353                        odvp->attr.store = NULL;
 354                        ret = sysfs_create_file(&priv->pdev->dev.kobj,
 355                                                &odvp->attr.attr);
 356                        if (ret)
 357                                goto out_err;
 358                }
 359        }
 360
 361        for (i = 0; i < obj->package.count; i++) {
 362                if (obj->package.elements[i].type == ACPI_TYPE_INTEGER)
 363                        priv->odvp[i] = obj->package.elements[i].integer.value;
 364        }
 365
 366        kfree(obj);
 367        return 0;
 368
 369out_err:
 370        cleanup_odvp(priv);
 371        kfree(obj);
 372        return ret;
 373}
 374
 375static void int3400_notify(acpi_handle handle,
 376                        u32 event,
 377                        void *data)
 378{
 379        struct int3400_thermal_priv *priv = data;
 380        char *thermal_prop[5];
 381        int therm_event;
 382
 383        if (!priv)
 384                return;
 385
 386        switch (event) {
 387        case INT3400_THERMAL_TABLE_CHANGED:
 388                therm_event = THERMAL_TABLE_CHANGED;
 389                break;
 390        case INT3400_KEEP_ALIVE:
 391                therm_event = THERMAL_EVENT_KEEP_ALIVE;
 392                break;
 393        case INT3400_ODVP_CHANGED:
 394                evaluate_odvp(priv);
 395                therm_event = THERMAL_DEVICE_POWER_CAPABILITY_CHANGED;
 396                break;
 397        default:
 398                /* Ignore unknown notification codes sent to INT3400 device */
 399                return;
 400        }
 401
 402        thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s", priv->thermal->type);
 403        thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d", priv->thermal->temperature);
 404        thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");
 405        thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d", therm_event);
 406        thermal_prop[4] = NULL;
 407        kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE, thermal_prop);
 408}
 409
 410static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
 411                        int *temp)
 412{
 413        *temp = 20 * 1000; /* faked temp sensor with 20C */
 414        return 0;
 415}
 416
 417static int int3400_thermal_change_mode(struct thermal_zone_device *thermal,
 418                                       enum thermal_device_mode mode)
 419{
 420        struct int3400_thermal_priv *priv = thermal->devdata;
 421        int result = 0;
 422
 423        if (!priv)
 424                return -EINVAL;
 425
 426        if (mode != thermal->mode)
 427                result = int3400_thermal_run_osc(priv->adev->handle,
 428                                                priv->current_uuid_index,
 429                                                mode == THERMAL_DEVICE_ENABLED);
 430
 431
 432        evaluate_odvp(priv);
 433
 434        return result;
 435}
 436
 437static struct thermal_zone_device_ops int3400_thermal_ops = {
 438        .get_temp = int3400_thermal_get_temp,
 439        .change_mode = int3400_thermal_change_mode,
 440};
 441
 442static struct thermal_zone_params int3400_thermal_params = {
 443        .governor_name = "user_space",
 444        .no_hwmon = true,
 445};
 446
 447static void int3400_setup_gddv(struct int3400_thermal_priv *priv)
 448{
 449        struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
 450        union acpi_object *obj;
 451        acpi_status status;
 452
 453        status = acpi_evaluate_object(priv->adev->handle, "GDDV", NULL,
 454                                      &buffer);
 455        if (ACPI_FAILURE(status) || !buffer.length)
 456                return;
 457
 458        obj = buffer.pointer;
 459        if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 1
 460            || obj->package.elements[0].type != ACPI_TYPE_BUFFER) {
 461                kfree(buffer.pointer);
 462                return;
 463        }
 464
 465        priv->data_vault = kmemdup(obj->package.elements[0].buffer.pointer,
 466                                   obj->package.elements[0].buffer.length,
 467                                   GFP_KERNEL);
 468        bin_attr_data_vault.private = priv->data_vault;
 469        bin_attr_data_vault.size = obj->package.elements[0].buffer.length;
 470        kfree(buffer.pointer);
 471}
 472
 473static int int3400_thermal_probe(struct platform_device *pdev)
 474{
 475        struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
 476        struct int3400_thermal_priv *priv;
 477        int result;
 478
 479        if (!adev)
 480                return -ENODEV;
 481
 482        priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL);
 483        if (!priv)
 484                return -ENOMEM;
 485
 486        priv->pdev = pdev;
 487        priv->adev = adev;
 488
 489        result = int3400_thermal_get_uuids(priv);
 490
 491        /* Missing IDSP isn't fatal */
 492        if (result && result != -ENODEV)
 493                goto free_priv;
 494
 495        priv->current_uuid_index = -1;
 496
 497        result = acpi_parse_art(priv->adev->handle, &priv->art_count,
 498                                &priv->arts, true);
 499        if (result)
 500                dev_dbg(&pdev->dev, "_ART table parsing error\n");
 501
 502        result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,
 503                                &priv->trts, true);
 504        if (result)
 505                dev_dbg(&pdev->dev, "_TRT table parsing error\n");
 506
 507        platform_set_drvdata(pdev, priv);
 508
 509        int3400_setup_gddv(priv);
 510
 511        evaluate_odvp(priv);
 512
 513        priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
 514                                                priv, &int3400_thermal_ops,
 515                                                &int3400_thermal_params, 0, 0);
 516        if (IS_ERR(priv->thermal)) {
 517                result = PTR_ERR(priv->thermal);
 518                goto free_art_trt;
 519        }
 520
 521        priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
 522                                                        priv->adev->handle);
 523
 524        result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);
 525        if (result)
 526                goto free_rel_misc;
 527
 528        if (acpi_has_method(priv->adev->handle, "IMOK")) {
 529                result = sysfs_create_group(&pdev->dev.kobj, &imok_attribute_group);
 530                if (result)
 531                        goto free_imok;
 532        }
 533
 534        if (priv->data_vault) {
 535                result = sysfs_create_group(&pdev->dev.kobj,
 536                                            &data_attribute_group);
 537                if (result)
 538                        goto free_uuid;
 539        }
 540
 541        result = acpi_install_notify_handler(
 542                        priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
 543                        (void *)priv);
 544        if (result)
 545                goto free_sysfs;
 546
 547        return 0;
 548
 549free_sysfs:
 550        cleanup_odvp(priv);
 551        if (priv->data_vault) {
 552                sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
 553                kfree(priv->data_vault);
 554        }
 555free_uuid:
 556        sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
 557free_imok:
 558        sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
 559free_rel_misc:
 560        if (!priv->rel_misc_dev_res)
 561                acpi_thermal_rel_misc_device_remove(priv->adev->handle);
 562        thermal_zone_device_unregister(priv->thermal);
 563free_art_trt:
 564        kfree(priv->trts);
 565        kfree(priv->arts);
 566free_priv:
 567        kfree(priv);
 568        return result;
 569}
 570
 571static int int3400_thermal_remove(struct platform_device *pdev)
 572{
 573        struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
 574
 575        acpi_remove_notify_handler(
 576                        priv->adev->handle, ACPI_DEVICE_NOTIFY,
 577                        int3400_notify);
 578
 579        cleanup_odvp(priv);
 580
 581        if (!priv->rel_misc_dev_res)
 582                acpi_thermal_rel_misc_device_remove(priv->adev->handle);
 583
 584        if (priv->data_vault)
 585                sysfs_remove_group(&pdev->dev.kobj, &data_attribute_group);
 586        sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
 587        sysfs_remove_group(&pdev->dev.kobj, &imok_attribute_group);
 588        thermal_zone_device_unregister(priv->thermal);
 589        kfree(priv->data_vault);
 590        kfree(priv->trts);
 591        kfree(priv->arts);
 592        kfree(priv);
 593        return 0;
 594}
 595
 596static const struct acpi_device_id int3400_thermal_match[] = {
 597        {"INT3400", 0},
 598        {"INTC1040", 0},
 599        {"INTC1041", 0},
 600        {}
 601};
 602
 603MODULE_DEVICE_TABLE(acpi, int3400_thermal_match);
 604
 605static struct platform_driver int3400_thermal_driver = {
 606        .probe = int3400_thermal_probe,
 607        .remove = int3400_thermal_remove,
 608        .driver = {
 609                   .name = "int3400 thermal",
 610                   .acpi_match_table = ACPI_PTR(int3400_thermal_match),
 611                   },
 612};
 613
 614module_platform_driver(int3400_thermal_driver);
 615
 616MODULE_DESCRIPTION("INT3400 Thermal driver");
 617MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
 618MODULE_LICENSE("GPL");
 619