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
  17enum int3400_thermal_uuid {
  18        INT3400_THERMAL_PASSIVE_1,
  19        INT3400_THERMAL_ACTIVE,
  20        INT3400_THERMAL_CRITICAL,
  21        INT3400_THERMAL_ADAPTIVE_PERFORMANCE,
  22        INT3400_THERMAL_EMERGENCY_CALL_MODE,
  23        INT3400_THERMAL_PASSIVE_2,
  24        INT3400_THERMAL_POWER_BOSS,
  25        INT3400_THERMAL_VIRTUAL_SENSOR,
  26        INT3400_THERMAL_COOLING_MODE,
  27        INT3400_THERMAL_HARDWARE_DUTY_CYCLING,
  28        INT3400_THERMAL_MAXIMUM_UUID,
  29};
  30
  31static char *int3400_thermal_uuids[INT3400_THERMAL_MAXIMUM_UUID] = {
  32        "42A441D6-AE6A-462b-A84B-4A8CE79027D3",
  33        "3A95C389-E4B8-4629-A526-C52C88626BAE",
  34        "97C68AE7-15FA-499c-B8C9-5DA81D606E0A",
  35        "63BE270F-1C11-48FD-A6F7-3AF253FF3E2D",
  36        "5349962F-71E6-431D-9AE8-0A635B710AEE",
  37        "9E04115A-AE87-4D1C-9500-0F3E340BFE75",
  38        "F5A35014-C209-46A4-993A-EB56DE7530A1",
  39        "6ED722A7-9240-48A5-B479-31EEF723D7CF",
  40        "16CAF1B7-DD38-40ED-B1C1-1B8A1913D531",
  41        "BE84BABF-C4D4-403D-B495-3128FD44dAC1",
  42};
  43
  44struct int3400_thermal_priv {
  45        struct acpi_device *adev;
  46        struct thermal_zone_device *thermal;
  47        int mode;
  48        int art_count;
  49        struct art *arts;
  50        int trt_count;
  51        struct trt *trts;
  52        u8 uuid_bitmap;
  53        int rel_misc_dev_res;
  54        int current_uuid_index;
  55};
  56
  57static ssize_t available_uuids_show(struct device *dev,
  58                                    struct device_attribute *attr,
  59                                    char *buf)
  60{
  61        struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
  62        int i;
  63        int length = 0;
  64
  65        for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; i++) {
  66                if (priv->uuid_bitmap & (1 << i))
  67                        if (PAGE_SIZE - length > 0)
  68                                length += snprintf(&buf[length],
  69                                                   PAGE_SIZE - length,
  70                                                   "%s\n",
  71                                                   int3400_thermal_uuids[i]);
  72        }
  73
  74        return length;
  75}
  76
  77static ssize_t current_uuid_show(struct device *dev,
  78                                 struct device_attribute *devattr, char *buf)
  79{
  80        struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
  81
  82        if (priv->uuid_bitmap & (1 << priv->current_uuid_index))
  83                return sprintf(buf, "%s\n",
  84                               int3400_thermal_uuids[priv->current_uuid_index]);
  85        else
  86                return sprintf(buf, "INVALID\n");
  87}
  88
  89static ssize_t current_uuid_store(struct device *dev,
  90                                  struct device_attribute *attr,
  91                                  const char *buf, size_t count)
  92{
  93        struct int3400_thermal_priv *priv = dev_get_drvdata(dev);
  94        int i;
  95
  96        for (i = 0; i < INT3400_THERMAL_MAXIMUM_UUID; ++i) {
  97                if ((priv->uuid_bitmap & (1 << i)) &&
  98                    !(strncmp(buf, int3400_thermal_uuids[i],
  99                              sizeof(int3400_thermal_uuids[i]) - 1))) {
 100                        priv->current_uuid_index = i;
 101                        return count;
 102                }
 103        }
 104
 105        return -EINVAL;
 106}
 107
 108static DEVICE_ATTR_RW(current_uuid);
 109static DEVICE_ATTR_RO(available_uuids);
 110static struct attribute *uuid_attrs[] = {
 111        &dev_attr_available_uuids.attr,
 112        &dev_attr_current_uuid.attr,
 113        NULL
 114};
 115
 116static const struct attribute_group uuid_attribute_group = {
 117        .attrs = uuid_attrs,
 118        .name = "uuids"
 119};
 120
 121static int int3400_thermal_get_uuids(struct int3400_thermal_priv *priv)
 122{
 123        struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL};
 124        union acpi_object *obja, *objb;
 125        int i, j;
 126        int result = 0;
 127        acpi_status status;
 128
 129        status = acpi_evaluate_object(priv->adev->handle, "IDSP", NULL, &buf);
 130        if (ACPI_FAILURE(status))
 131                return -ENODEV;
 132
 133        obja = (union acpi_object *)buf.pointer;
 134        if (obja->type != ACPI_TYPE_PACKAGE) {
 135                result = -EINVAL;
 136                goto end;
 137        }
 138
 139        for (i = 0; i < obja->package.count; i++) {
 140                objb = &obja->package.elements[i];
 141                if (objb->type != ACPI_TYPE_BUFFER) {
 142                        result = -EINVAL;
 143                        goto end;
 144                }
 145
 146                /* UUID must be 16 bytes */
 147                if (objb->buffer.length != 16) {
 148                        result = -EINVAL;
 149                        goto end;
 150                }
 151
 152                for (j = 0; j < INT3400_THERMAL_MAXIMUM_UUID; j++) {
 153                        guid_t guid;
 154
 155                        guid_parse(int3400_thermal_uuids[j], &guid);
 156                        if (guid_equal((guid_t *)objb->buffer.pointer, &guid)) {
 157                                priv->uuid_bitmap |= (1 << j);
 158                                break;
 159                        }
 160                }
 161        }
 162
 163end:
 164        kfree(buf.pointer);
 165        return result;
 166}
 167
 168static int int3400_thermal_run_osc(acpi_handle handle,
 169                                enum int3400_thermal_uuid uuid, bool enable)
 170{
 171        u32 ret, buf[2];
 172        acpi_status status;
 173        int result = 0;
 174        struct acpi_osc_context context = {
 175                .uuid_str = int3400_thermal_uuids[uuid],
 176                .rev = 1,
 177                .cap.length = 8,
 178        };
 179
 180        buf[OSC_QUERY_DWORD] = 0;
 181        buf[OSC_SUPPORT_DWORD] = enable;
 182
 183        context.cap.pointer = buf;
 184
 185        status = acpi_run_osc(handle, &context);
 186        if (ACPI_SUCCESS(status)) {
 187                ret = *((u32 *)(context.ret.pointer + 4));
 188                if (ret != enable)
 189                        result = -EPERM;
 190        } else
 191                result = -EPERM;
 192
 193        kfree(context.ret.pointer);
 194        return result;
 195}
 196
 197static void int3400_notify(acpi_handle handle,
 198                        u32 event,
 199                        void *data)
 200{
 201        struct int3400_thermal_priv *priv = data;
 202        char *thermal_prop[5];
 203
 204        if (!priv)
 205                return;
 206
 207        switch (event) {
 208        case INT3400_THERMAL_TABLE_CHANGED:
 209                thermal_prop[0] = kasprintf(GFP_KERNEL, "NAME=%s",
 210                                priv->thermal->type);
 211                thermal_prop[1] = kasprintf(GFP_KERNEL, "TEMP=%d",
 212                                priv->thermal->temperature);
 213                thermal_prop[2] = kasprintf(GFP_KERNEL, "TRIP=");
 214                thermal_prop[3] = kasprintf(GFP_KERNEL, "EVENT=%d",
 215                                THERMAL_TABLE_CHANGED);
 216                thermal_prop[4] = NULL;
 217                kobject_uevent_env(&priv->thermal->device.kobj, KOBJ_CHANGE,
 218                                thermal_prop);
 219                break;
 220        default:
 221                /* Ignore unknown notification codes sent to INT3400 device */
 222                break;
 223        }
 224}
 225
 226static int int3400_thermal_get_temp(struct thermal_zone_device *thermal,
 227                        int *temp)
 228{
 229        *temp = 20 * 1000; /* faked temp sensor with 20C */
 230        return 0;
 231}
 232
 233static int int3400_thermal_get_mode(struct thermal_zone_device *thermal,
 234                                enum thermal_device_mode *mode)
 235{
 236        struct int3400_thermal_priv *priv = thermal->devdata;
 237
 238        if (!priv)
 239                return -EINVAL;
 240
 241        *mode = priv->mode;
 242
 243        return 0;
 244}
 245
 246static int int3400_thermal_set_mode(struct thermal_zone_device *thermal,
 247                                enum thermal_device_mode mode)
 248{
 249        struct int3400_thermal_priv *priv = thermal->devdata;
 250        bool enable;
 251        int result = 0;
 252
 253        if (!priv)
 254                return -EINVAL;
 255
 256        if (mode == THERMAL_DEVICE_ENABLED)
 257                enable = true;
 258        else if (mode == THERMAL_DEVICE_DISABLED)
 259                enable = false;
 260        else
 261                return -EINVAL;
 262
 263        if (enable != priv->mode) {
 264                priv->mode = enable;
 265                result = int3400_thermal_run_osc(priv->adev->handle,
 266                                                 priv->current_uuid_index,
 267                                                 enable);
 268        }
 269        return result;
 270}
 271
 272static struct thermal_zone_device_ops int3400_thermal_ops = {
 273        .get_temp = int3400_thermal_get_temp,
 274};
 275
 276static struct thermal_zone_params int3400_thermal_params = {
 277        .governor_name = "user_space",
 278        .no_hwmon = true,
 279};
 280
 281static int int3400_thermal_probe(struct platform_device *pdev)
 282{
 283        struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
 284        struct int3400_thermal_priv *priv;
 285        int result;
 286
 287        if (!adev)
 288                return -ENODEV;
 289
 290        priv = kzalloc(sizeof(struct int3400_thermal_priv), GFP_KERNEL);
 291        if (!priv)
 292                return -ENOMEM;
 293
 294        priv->adev = adev;
 295
 296        result = int3400_thermal_get_uuids(priv);
 297        if (result)
 298                goto free_priv;
 299
 300        result = acpi_parse_art(priv->adev->handle, &priv->art_count,
 301                                &priv->arts, true);
 302        if (result)
 303                dev_dbg(&pdev->dev, "_ART table parsing error\n");
 304
 305        result = acpi_parse_trt(priv->adev->handle, &priv->trt_count,
 306                                &priv->trts, true);
 307        if (result)
 308                dev_dbg(&pdev->dev, "_TRT table parsing error\n");
 309
 310        platform_set_drvdata(pdev, priv);
 311
 312        int3400_thermal_ops.get_mode = int3400_thermal_get_mode;
 313        int3400_thermal_ops.set_mode = int3400_thermal_set_mode;
 314
 315        priv->thermal = thermal_zone_device_register("INT3400 Thermal", 0, 0,
 316                                                priv, &int3400_thermal_ops,
 317                                                &int3400_thermal_params, 0, 0);
 318        if (IS_ERR(priv->thermal)) {
 319                result = PTR_ERR(priv->thermal);
 320                goto free_art_trt;
 321        }
 322
 323        priv->rel_misc_dev_res = acpi_thermal_rel_misc_device_add(
 324                                                        priv->adev->handle);
 325
 326        result = sysfs_create_group(&pdev->dev.kobj, &uuid_attribute_group);
 327        if (result)
 328                goto free_rel_misc;
 329
 330        result = acpi_install_notify_handler(
 331                        priv->adev->handle, ACPI_DEVICE_NOTIFY, int3400_notify,
 332                        (void *)priv);
 333        if (result)
 334                goto free_sysfs;
 335
 336        return 0;
 337
 338free_sysfs:
 339        sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
 340free_rel_misc:
 341        if (!priv->rel_misc_dev_res)
 342                acpi_thermal_rel_misc_device_remove(priv->adev->handle);
 343        thermal_zone_device_unregister(priv->thermal);
 344free_art_trt:
 345        kfree(priv->trts);
 346        kfree(priv->arts);
 347free_priv:
 348        kfree(priv);
 349        return result;
 350}
 351
 352static int int3400_thermal_remove(struct platform_device *pdev)
 353{
 354        struct int3400_thermal_priv *priv = platform_get_drvdata(pdev);
 355
 356        acpi_remove_notify_handler(
 357                        priv->adev->handle, ACPI_DEVICE_NOTIFY,
 358                        int3400_notify);
 359
 360        if (!priv->rel_misc_dev_res)
 361                acpi_thermal_rel_misc_device_remove(priv->adev->handle);
 362
 363        sysfs_remove_group(&pdev->dev.kobj, &uuid_attribute_group);
 364        thermal_zone_device_unregister(priv->thermal);
 365        kfree(priv->trts);
 366        kfree(priv->arts);
 367        kfree(priv);
 368        return 0;
 369}
 370
 371static const struct acpi_device_id int3400_thermal_match[] = {
 372        {"INT3400", 0},
 373        {}
 374};
 375
 376MODULE_DEVICE_TABLE(acpi, int3400_thermal_match);
 377
 378static struct platform_driver int3400_thermal_driver = {
 379        .probe = int3400_thermal_probe,
 380        .remove = int3400_thermal_remove,
 381        .driver = {
 382                   .name = "int3400 thermal",
 383                   .acpi_match_table = ACPI_PTR(int3400_thermal_match),
 384                   },
 385};
 386
 387module_platform_driver(int3400_thermal_driver);
 388
 389MODULE_DESCRIPTION("INT3400 Thermal driver");
 390MODULE_AUTHOR("Zhang Rui <rui.zhang@intel.com>");
 391MODULE_LICENSE("GPL");
 392