linux/drivers/thermal/da9062-thermal.c
<<
>>
Prefs
   1/*
   2 * Thermal device driver for DA9062 and DA9061
   3 * Copyright (C) 2017  Dialog Semiconductor
   4 *
   5 * This program is free software; you can redistribute it and/or
   6 * modify it under the terms of the GNU General Public License
   7 * as published by the Free Software Foundation; either version 2
   8 * of the License, or (at your option) any later version.
   9 *
  10 * This program is distributed in the hope that it will be useful,
  11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13 * GNU General Public License for more details.
  14 */
  15
  16/* When over-temperature is reached, an interrupt from the device will be
  17 * triggered. Following this event the interrupt will be disabled and
  18 * periodic transmission of uevents (HOT trip point) should define the
  19 * first level of temperature supervision. It is expected that any final
  20 * implementation of the thermal driver will include a .notify() function
  21 * to implement these uevents to userspace.
  22 *
  23 * These uevents are intended to indicate non-invasive temperature control
  24 * of the system, where the necessary measures for cooling are the
  25 * responsibility of the host software. Once the temperature falls again,
  26 * the IRQ is re-enabled so the start of a new over-temperature event can
  27 * be detected without constant software monitoring.
  28 */
  29
  30#include <linux/errno.h>
  31#include <linux/interrupt.h>
  32#include <linux/module.h>
  33#include <linux/of.h>
  34#include <linux/platform_device.h>
  35#include <linux/regmap.h>
  36#include <linux/thermal.h>
  37#include <linux/workqueue.h>
  38
  39#include <linux/mfd/da9062/core.h>
  40#include <linux/mfd/da9062/registers.h>
  41
  42/* Minimum, maximum and default polling millisecond periods are provided
  43 * here as an example. It is expected that any final implementation to also
  44 * include a modification of these settings to match the required
  45 * application.
  46 */
  47#define DA9062_DEFAULT_POLLING_MS_PERIOD        3000
  48#define DA9062_MAX_POLLING_MS_PERIOD            10000
  49#define DA9062_MIN_POLLING_MS_PERIOD            1000
  50
  51#define DA9062_MILLI_CELSIUS(t)                 ((t) * 1000)
  52
  53struct da9062_thermal_config {
  54        const char *name;
  55};
  56
  57struct da9062_thermal {
  58        struct da9062 *hw;
  59        struct delayed_work work;
  60        struct thermal_zone_device *zone;
  61        enum thermal_device_mode mode;
  62        struct mutex lock; /* protection for da9062_thermal temperature */
  63        int temperature;
  64        int irq;
  65        const struct da9062_thermal_config *config;
  66        struct device *dev;
  67};
  68
  69static void da9062_thermal_poll_on(struct work_struct *work)
  70{
  71        struct da9062_thermal *thermal = container_of(work,
  72                                                struct da9062_thermal,
  73                                                work.work);
  74        unsigned long delay;
  75        unsigned int val;
  76        int ret;
  77
  78        /* clear E_TEMP */
  79        ret = regmap_write(thermal->hw->regmap,
  80                           DA9062AA_EVENT_B,
  81                           DA9062AA_E_TEMP_MASK);
  82        if (ret < 0) {
  83                dev_err(thermal->dev,
  84                        "Cannot clear the TJUNC temperature status\n");
  85                goto err_enable_irq;
  86        }
  87
  88        /* Now read E_TEMP again: it is acting like a status bit.
  89         * If over-temperature, then this status will be true.
  90         * If not over-temperature, this status will be false.
  91         */
  92        ret = regmap_read(thermal->hw->regmap,
  93                          DA9062AA_EVENT_B,
  94                          &val);
  95        if (ret < 0) {
  96                dev_err(thermal->dev,
  97                        "Cannot check the TJUNC temperature status\n");
  98                goto err_enable_irq;
  99        }
 100
 101        if (val & DA9062AA_E_TEMP_MASK) {
 102                mutex_lock(&thermal->lock);
 103                thermal->temperature = DA9062_MILLI_CELSIUS(125);
 104                mutex_unlock(&thermal->lock);
 105                thermal_zone_device_update(thermal->zone,
 106                                           THERMAL_EVENT_UNSPECIFIED);
 107
 108                delay = msecs_to_jiffies(thermal->zone->passive_delay);
 109                schedule_delayed_work(&thermal->work, delay);
 110                return;
 111        }
 112
 113        mutex_lock(&thermal->lock);
 114        thermal->temperature = DA9062_MILLI_CELSIUS(0);
 115        mutex_unlock(&thermal->lock);
 116        thermal_zone_device_update(thermal->zone,
 117                                   THERMAL_EVENT_UNSPECIFIED);
 118
 119err_enable_irq:
 120        enable_irq(thermal->irq);
 121}
 122
 123static irqreturn_t da9062_thermal_irq_handler(int irq, void *data)
 124{
 125        struct da9062_thermal *thermal = data;
 126
 127        disable_irq_nosync(thermal->irq);
 128        schedule_delayed_work(&thermal->work, 0);
 129
 130        return IRQ_HANDLED;
 131}
 132
 133static int da9062_thermal_get_mode(struct thermal_zone_device *z,
 134                                   enum thermal_device_mode *mode)
 135{
 136        struct da9062_thermal *thermal = z->devdata;
 137        *mode = thermal->mode;
 138        return 0;
 139}
 140
 141static int da9062_thermal_get_trip_type(struct thermal_zone_device *z,
 142                                        int trip,
 143                                        enum thermal_trip_type *type)
 144{
 145        struct da9062_thermal *thermal = z->devdata;
 146
 147        switch (trip) {
 148        case 0:
 149                *type = THERMAL_TRIP_HOT;
 150                break;
 151        default:
 152                dev_err(thermal->dev,
 153                        "Driver does not support more than 1 trip-wire\n");
 154                return -EINVAL;
 155        }
 156
 157        return 0;
 158}
 159
 160static int da9062_thermal_get_trip_temp(struct thermal_zone_device *z,
 161                                        int trip,
 162                                        int *temp)
 163{
 164        struct da9062_thermal *thermal = z->devdata;
 165
 166        switch (trip) {
 167        case 0:
 168                *temp = DA9062_MILLI_CELSIUS(125);
 169                break;
 170        default:
 171                dev_err(thermal->dev,
 172                        "Driver does not support more than 1 trip-wire\n");
 173                return -EINVAL;
 174        }
 175
 176        return 0;
 177}
 178
 179static int da9062_thermal_get_temp(struct thermal_zone_device *z,
 180                                   int *temp)
 181{
 182        struct da9062_thermal *thermal = z->devdata;
 183
 184        mutex_lock(&thermal->lock);
 185        *temp = thermal->temperature;
 186        mutex_unlock(&thermal->lock);
 187
 188        return 0;
 189}
 190
 191static struct thermal_zone_device_ops da9062_thermal_ops = {
 192        .get_temp       = da9062_thermal_get_temp,
 193        .get_mode       = da9062_thermal_get_mode,
 194        .get_trip_type  = da9062_thermal_get_trip_type,
 195        .get_trip_temp  = da9062_thermal_get_trip_temp,
 196};
 197
 198static const struct da9062_thermal_config da9062_config = {
 199        .name = "da9062-thermal",
 200};
 201
 202static const struct of_device_id da9062_compatible_reg_id_table[] = {
 203        { .compatible = "dlg,da9062-thermal", .data = &da9062_config },
 204        { },
 205};
 206
 207MODULE_DEVICE_TABLE(of, da9062_compatible_reg_id_table);
 208
 209static int da9062_thermal_probe(struct platform_device *pdev)
 210{
 211        struct da9062 *chip = dev_get_drvdata(pdev->dev.parent);
 212        struct da9062_thermal *thermal;
 213        unsigned int pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD;
 214        const struct of_device_id *match;
 215        int ret = 0;
 216
 217        match = of_match_node(da9062_compatible_reg_id_table,
 218                              pdev->dev.of_node);
 219        if (!match)
 220                return -ENXIO;
 221
 222        if (pdev->dev.of_node) {
 223                if (!of_property_read_u32(pdev->dev.of_node,
 224                                          "polling-delay-passive",
 225                                          &pp_tmp)) {
 226                        if (pp_tmp < DA9062_MIN_POLLING_MS_PERIOD ||
 227                            pp_tmp > DA9062_MAX_POLLING_MS_PERIOD) {
 228                                dev_warn(&pdev->dev,
 229                                         "Out-of-range polling period %d ms\n",
 230                                         pp_tmp);
 231                                pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD;
 232                        }
 233                }
 234        }
 235
 236        thermal = devm_kzalloc(&pdev->dev, sizeof(struct da9062_thermal),
 237                               GFP_KERNEL);
 238        if (!thermal) {
 239                ret = -ENOMEM;
 240                goto err;
 241        }
 242
 243        thermal->config = match->data;
 244        thermal->hw = chip;
 245        thermal->mode = THERMAL_DEVICE_ENABLED;
 246        thermal->dev = &pdev->dev;
 247
 248        INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on);
 249        mutex_init(&thermal->lock);
 250
 251        thermal->zone = thermal_zone_device_register(thermal->config->name,
 252                                        1, 0, thermal,
 253                                        &da9062_thermal_ops, NULL, pp_tmp,
 254                                        0);
 255        if (IS_ERR(thermal->zone)) {
 256                dev_err(&pdev->dev, "Cannot register thermal zone device\n");
 257                ret = PTR_ERR(thermal->zone);
 258                goto err;
 259        }
 260
 261        dev_dbg(&pdev->dev,
 262                "TJUNC temperature polling period set at %d ms\n",
 263                thermal->zone->passive_delay);
 264
 265        ret = platform_get_irq_byname(pdev, "THERMAL");
 266        if (ret < 0) {
 267                dev_err(&pdev->dev, "Failed to get platform IRQ.\n");
 268                goto err_zone;
 269        }
 270        thermal->irq = ret;
 271
 272        ret = request_threaded_irq(thermal->irq, NULL,
 273                                   da9062_thermal_irq_handler,
 274                                   IRQF_TRIGGER_LOW | IRQF_ONESHOT,
 275                                   "THERMAL", thermal);
 276        if (ret) {
 277                dev_err(&pdev->dev,
 278                        "Failed to request thermal device IRQ.\n");
 279                goto err_zone;
 280        }
 281
 282        platform_set_drvdata(pdev, thermal);
 283        return 0;
 284
 285err_zone:
 286        thermal_zone_device_unregister(thermal->zone);
 287err:
 288        return ret;
 289}
 290
 291static int da9062_thermal_remove(struct platform_device *pdev)
 292{
 293        struct  da9062_thermal *thermal = platform_get_drvdata(pdev);
 294
 295        free_irq(thermal->irq, thermal);
 296        cancel_delayed_work_sync(&thermal->work);
 297        thermal_zone_device_unregister(thermal->zone);
 298        return 0;
 299}
 300
 301static struct platform_driver da9062_thermal_driver = {
 302        .probe  = da9062_thermal_probe,
 303        .remove = da9062_thermal_remove,
 304        .driver = {
 305                .name   = "da9062-thermal",
 306                .of_match_table = da9062_compatible_reg_id_table,
 307        },
 308};
 309
 310module_platform_driver(da9062_thermal_driver);
 311
 312MODULE_AUTHOR("Steve Twiss");
 313MODULE_DESCRIPTION("Thermal TJUNC device driver for Dialog DA9062 and DA9061");
 314MODULE_LICENSE("GPL");
 315MODULE_ALIAS("platform:da9062-thermal");
 316