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