linux/drivers/thermal/int340x_thermal/int3406_thermal.c
<<
>>
Prefs
   1/*
   2 * INT3406 thermal driver for display participant device
   3 *
   4 * Copyright (C) 2016, Intel Corporation
   5 * Authors: Aaron Lu <aaron.lu@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/backlight.h>
  17#include <linux/thermal.h>
  18#include <acpi/video.h>
  19
  20#define INT3406_BRIGHTNESS_LIMITS_CHANGED       0x80
  21
  22struct int3406_thermal_data {
  23        int upper_limit;
  24        int lower_limit;
  25        acpi_handle handle;
  26        struct acpi_video_device_brightness *br;
  27        struct backlight_device *raw_bd;
  28        struct thermal_cooling_device *cooling_dev;
  29};
  30
  31/*
  32 * According to the ACPI spec,
  33 * "Each brightness level is represented by a number between 0 and 100,
  34 * and can be thought of as a percentage. For example, 50 can be 50%
  35 * power consumption or 50% brightness, as defined by the OEM."
  36 *
  37 * As int3406 device uses this value to communicate with the native
  38 * graphics driver, we make the assumption that it represents
  39 * the percentage of brightness only
  40 */
  41#define ACPI_TO_RAW(v, d) (d->raw_bd->props.max_brightness * v / 100)
  42#define RAW_TO_ACPI(v, d) (v * 100 / d->raw_bd->props.max_brightness)
  43
  44static int
  45int3406_thermal_get_max_state(struct thermal_cooling_device *cooling_dev,
  46                              unsigned long *state)
  47{
  48        struct int3406_thermal_data *d = cooling_dev->devdata;
  49
  50        *state = d->upper_limit - d->lower_limit;
  51        return 0;
  52}
  53
  54static int
  55int3406_thermal_set_cur_state(struct thermal_cooling_device *cooling_dev,
  56                              unsigned long state)
  57{
  58        struct int3406_thermal_data *d = cooling_dev->devdata;
  59        int acpi_level, raw_level;
  60
  61        if (state > d->upper_limit - d->lower_limit)
  62                return -EINVAL;
  63
  64        acpi_level = d->br->levels[d->upper_limit - state];
  65
  66        raw_level = ACPI_TO_RAW(acpi_level, d);
  67
  68        return backlight_device_set_brightness(d->raw_bd, raw_level);
  69}
  70
  71static int
  72int3406_thermal_get_cur_state(struct thermal_cooling_device *cooling_dev,
  73                              unsigned long *state)
  74{
  75        struct int3406_thermal_data *d = cooling_dev->devdata;
  76        int acpi_level;
  77        int index;
  78
  79        acpi_level = RAW_TO_ACPI(d->raw_bd->props.brightness, d);
  80
  81        /*
  82         * There is no 1:1 mapping between the firmware interface level
  83         * with the raw interface level, we will have to find one that is
  84         * right above it.
  85         */
  86        for (index = d->lower_limit; index < d->upper_limit; index++) {
  87                if (acpi_level <= d->br->levels[index])
  88                        break;
  89        }
  90
  91        *state = d->upper_limit - index;
  92        return 0;
  93}
  94
  95static const struct thermal_cooling_device_ops video_cooling_ops = {
  96        .get_max_state = int3406_thermal_get_max_state,
  97        .get_cur_state = int3406_thermal_get_cur_state,
  98        .set_cur_state = int3406_thermal_set_cur_state,
  99};
 100
 101static int int3406_thermal_get_index(int *array, int nr, int value)
 102{
 103        int i;
 104
 105        for (i = 2; i < nr; i++) {
 106                if (array[i] == value)
 107                        break;
 108        }
 109        return i == nr ? -ENOENT : i;
 110}
 111
 112static void int3406_thermal_get_limit(struct int3406_thermal_data *d)
 113{
 114        acpi_status status;
 115        unsigned long long lower_limit, upper_limit;
 116
 117        status = acpi_evaluate_integer(d->handle, "DDDL", NULL, &lower_limit);
 118        if (ACPI_SUCCESS(status))
 119                d->lower_limit = int3406_thermal_get_index(d->br->levels,
 120                                        d->br->count, lower_limit);
 121
 122        status = acpi_evaluate_integer(d->handle, "DDPC", NULL, &upper_limit);
 123        if (ACPI_SUCCESS(status))
 124                d->upper_limit = int3406_thermal_get_index(d->br->levels,
 125                                        d->br->count, upper_limit);
 126
 127        /* lower_limit and upper_limit should be always set */
 128        d->lower_limit = d->lower_limit > 0 ? d->lower_limit : 2;
 129        d->upper_limit = d->upper_limit > 0 ? d->upper_limit : d->br->count - 1;
 130}
 131
 132static void int3406_notify(acpi_handle handle, u32 event, void *data)
 133{
 134        if (event == INT3406_BRIGHTNESS_LIMITS_CHANGED)
 135                int3406_thermal_get_limit(data);
 136}
 137
 138static int int3406_thermal_probe(struct platform_device *pdev)
 139{
 140        struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
 141        struct int3406_thermal_data *d;
 142        struct backlight_device *bd;
 143        int ret;
 144
 145        if (!ACPI_HANDLE(&pdev->dev))
 146                return -ENODEV;
 147
 148        d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
 149        if (!d)
 150                return -ENOMEM;
 151        d->handle = ACPI_HANDLE(&pdev->dev);
 152
 153        bd = backlight_device_get_by_type(BACKLIGHT_RAW);
 154        if (!bd)
 155                return -ENODEV;
 156        d->raw_bd = bd;
 157
 158        ret = acpi_video_get_levels(ACPI_COMPANION(&pdev->dev), &d->br, NULL);
 159        if (ret)
 160                return ret;
 161
 162        int3406_thermal_get_limit(d);
 163
 164        d->cooling_dev = thermal_cooling_device_register(acpi_device_bid(adev),
 165                                                         d, &video_cooling_ops);
 166        if (IS_ERR(d->cooling_dev))
 167                goto err;
 168
 169        ret = acpi_install_notify_handler(adev->handle, ACPI_DEVICE_NOTIFY,
 170                                          int3406_notify, d);
 171        if (ret)
 172                goto err_cdev;
 173
 174        platform_set_drvdata(pdev, d);
 175
 176        return 0;
 177
 178err_cdev:
 179        thermal_cooling_device_unregister(d->cooling_dev);
 180err:
 181        kfree(d->br);
 182        return -ENODEV;
 183}
 184
 185static int int3406_thermal_remove(struct platform_device *pdev)
 186{
 187        struct int3406_thermal_data *d = platform_get_drvdata(pdev);
 188
 189        thermal_cooling_device_unregister(d->cooling_dev);
 190        kfree(d->br);
 191        return 0;
 192}
 193
 194static const struct acpi_device_id int3406_thermal_match[] = {
 195        {"INT3406", 0},
 196        {}
 197};
 198
 199MODULE_DEVICE_TABLE(acpi, int3406_thermal_match);
 200
 201static struct platform_driver int3406_thermal_driver = {
 202        .probe = int3406_thermal_probe,
 203        .remove = int3406_thermal_remove,
 204        .driver = {
 205                   .name = "int3406 thermal",
 206                   .acpi_match_table = int3406_thermal_match,
 207                   },
 208};
 209
 210module_platform_driver(int3406_thermal_driver);
 211
 212MODULE_DESCRIPTION("INT3406 Thermal driver");
 213MODULE_LICENSE("GPL v2");
 214