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