root/drivers/thermal/da9062-thermal.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. da9062_thermal_poll_on
  2. da9062_thermal_irq_handler
  3. da9062_thermal_get_mode
  4. da9062_thermal_get_trip_type
  5. da9062_thermal_get_trip_temp
  6. da9062_thermal_get_temp
  7. da9062_thermal_probe
  8. da9062_thermal_remove

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

/* [<][>][^][v][top][bottom][index][help] */