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