da9062-thermal.c (7886B)
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 44struct da9062_thermal_config { 45 const char *name; 46}; 47 48struct da9062_thermal { 49 struct da9062 *hw; 50 struct delayed_work work; 51 struct thermal_zone_device *zone; 52 struct mutex lock; /* protection for da9062_thermal temperature */ 53 int temperature; 54 int irq; 55 const struct da9062_thermal_config *config; 56 struct device *dev; 57}; 58 59static void da9062_thermal_poll_on(struct work_struct *work) 60{ 61 struct da9062_thermal *thermal = container_of(work, 62 struct da9062_thermal, 63 work.work); 64 unsigned long delay; 65 unsigned int val; 66 int ret; 67 68 /* clear E_TEMP */ 69 ret = regmap_write(thermal->hw->regmap, 70 DA9062AA_EVENT_B, 71 DA9062AA_E_TEMP_MASK); 72 if (ret < 0) { 73 dev_err(thermal->dev, 74 "Cannot clear the TJUNC temperature status\n"); 75 goto err_enable_irq; 76 } 77 78 /* Now read E_TEMP again: it is acting like a status bit. 79 * If over-temperature, then this status will be true. 80 * If not over-temperature, this status will be false. 81 */ 82 ret = regmap_read(thermal->hw->regmap, 83 DA9062AA_EVENT_B, 84 &val); 85 if (ret < 0) { 86 dev_err(thermal->dev, 87 "Cannot check the TJUNC temperature status\n"); 88 goto err_enable_irq; 89 } 90 91 if (val & DA9062AA_E_TEMP_MASK) { 92 mutex_lock(&thermal->lock); 93 thermal->temperature = DA9062_MILLI_CELSIUS(125); 94 mutex_unlock(&thermal->lock); 95 thermal_zone_device_update(thermal->zone, 96 THERMAL_EVENT_UNSPECIFIED); 97 98 delay = thermal->zone->passive_delay_jiffies; 99 queue_delayed_work(system_freezable_wq, &thermal->work, delay); 100 return; 101 } 102 103 mutex_lock(&thermal->lock); 104 thermal->temperature = DA9062_MILLI_CELSIUS(0); 105 mutex_unlock(&thermal->lock); 106 thermal_zone_device_update(thermal->zone, 107 THERMAL_EVENT_UNSPECIFIED); 108 109err_enable_irq: 110 enable_irq(thermal->irq); 111} 112 113static irqreturn_t da9062_thermal_irq_handler(int irq, void *data) 114{ 115 struct da9062_thermal *thermal = data; 116 117 disable_irq_nosync(thermal->irq); 118 queue_delayed_work(system_freezable_wq, &thermal->work, 0); 119 120 return IRQ_HANDLED; 121} 122 123static int da9062_thermal_get_trip_type(struct thermal_zone_device *z, 124 int trip, 125 enum thermal_trip_type *type) 126{ 127 struct da9062_thermal *thermal = z->devdata; 128 129 switch (trip) { 130 case 0: 131 *type = THERMAL_TRIP_HOT; 132 break; 133 default: 134 dev_err(thermal->dev, 135 "Driver does not support more than 1 trip-wire\n"); 136 return -EINVAL; 137 } 138 139 return 0; 140} 141 142static int da9062_thermal_get_trip_temp(struct thermal_zone_device *z, 143 int trip, 144 int *temp) 145{ 146 struct da9062_thermal *thermal = z->devdata; 147 148 switch (trip) { 149 case 0: 150 *temp = DA9062_MILLI_CELSIUS(125); 151 break; 152 default: 153 dev_err(thermal->dev, 154 "Driver does not support more than 1 trip-wire\n"); 155 return -EINVAL; 156 } 157 158 return 0; 159} 160 161static int da9062_thermal_get_temp(struct thermal_zone_device *z, 162 int *temp) 163{ 164 struct da9062_thermal *thermal = z->devdata; 165 166 mutex_lock(&thermal->lock); 167 *temp = thermal->temperature; 168 mutex_unlock(&thermal->lock); 169 170 return 0; 171} 172 173static struct thermal_zone_device_ops da9062_thermal_ops = { 174 .get_temp = da9062_thermal_get_temp, 175 .get_trip_type = da9062_thermal_get_trip_type, 176 .get_trip_temp = da9062_thermal_get_trip_temp, 177}; 178 179static const struct da9062_thermal_config da9062_config = { 180 .name = "da9062-thermal", 181}; 182 183static const struct of_device_id da9062_compatible_reg_id_table[] = { 184 { .compatible = "dlg,da9062-thermal", .data = &da9062_config }, 185 { }, 186}; 187 188MODULE_DEVICE_TABLE(of, da9062_compatible_reg_id_table); 189 190static int da9062_thermal_probe(struct platform_device *pdev) 191{ 192 struct da9062 *chip = dev_get_drvdata(pdev->dev.parent); 193 struct da9062_thermal *thermal; 194 unsigned int pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD; 195 const struct of_device_id *match; 196 int ret = 0; 197 198 match = of_match_node(da9062_compatible_reg_id_table, 199 pdev->dev.of_node); 200 if (!match) 201 return -ENXIO; 202 203 if (pdev->dev.of_node) { 204 if (!of_property_read_u32(pdev->dev.of_node, 205 "polling-delay-passive", 206 &pp_tmp)) { 207 if (pp_tmp < DA9062_MIN_POLLING_MS_PERIOD || 208 pp_tmp > DA9062_MAX_POLLING_MS_PERIOD) { 209 dev_warn(&pdev->dev, 210 "Out-of-range polling period %d ms\n", 211 pp_tmp); 212 pp_tmp = DA9062_DEFAULT_POLLING_MS_PERIOD; 213 } 214 } 215 } 216 217 thermal = devm_kzalloc(&pdev->dev, sizeof(struct da9062_thermal), 218 GFP_KERNEL); 219 if (!thermal) { 220 ret = -ENOMEM; 221 goto err; 222 } 223 224 thermal->config = match->data; 225 thermal->hw = chip; 226 thermal->dev = &pdev->dev; 227 228 INIT_DELAYED_WORK(&thermal->work, da9062_thermal_poll_on); 229 mutex_init(&thermal->lock); 230 231 thermal->zone = thermal_zone_device_register(thermal->config->name, 232 1, 0, thermal, 233 &da9062_thermal_ops, NULL, pp_tmp, 234 0); 235 if (IS_ERR(thermal->zone)) { 236 dev_err(&pdev->dev, "Cannot register thermal zone device\n"); 237 ret = PTR_ERR(thermal->zone); 238 goto err; 239 } 240 ret = thermal_zone_device_enable(thermal->zone); 241 if (ret) { 242 dev_err(&pdev->dev, "Cannot enable thermal zone device\n"); 243 goto err_zone; 244 } 245 246 dev_dbg(&pdev->dev, 247 "TJUNC temperature polling period set at %d ms\n", 248 jiffies_to_msecs(thermal->zone->passive_delay_jiffies)); 249 250 ret = platform_get_irq_byname(pdev, "THERMAL"); 251 if (ret < 0) { 252 dev_err(&pdev->dev, "Failed to get platform IRQ.\n"); 253 goto err_zone; 254 } 255 thermal->irq = ret; 256 257 ret = request_threaded_irq(thermal->irq, NULL, 258 da9062_thermal_irq_handler, 259 IRQF_TRIGGER_LOW | IRQF_ONESHOT, 260 "THERMAL", thermal); 261 if (ret) { 262 dev_err(&pdev->dev, 263 "Failed to request thermal device IRQ.\n"); 264 goto err_zone; 265 } 266 267 platform_set_drvdata(pdev, thermal); 268 return 0; 269 270err_zone: 271 thermal_zone_device_unregister(thermal->zone); 272err: 273 return ret; 274} 275 276static int da9062_thermal_remove(struct platform_device *pdev) 277{ 278 struct da9062_thermal *thermal = platform_get_drvdata(pdev); 279 280 free_irq(thermal->irq, thermal); 281 cancel_delayed_work_sync(&thermal->work); 282 thermal_zone_device_unregister(thermal->zone); 283 return 0; 284} 285 286static struct platform_driver da9062_thermal_driver = { 287 .probe = da9062_thermal_probe, 288 .remove = da9062_thermal_remove, 289 .driver = { 290 .name = "da9062-thermal", 291 .of_match_table = da9062_compatible_reg_id_table, 292 }, 293}; 294 295module_platform_driver(da9062_thermal_driver); 296 297MODULE_AUTHOR("Steve Twiss"); 298MODULE_DESCRIPTION("Thermal TJUNC device driver for Dialog DA9062 and DA9061"); 299MODULE_LICENSE("GPL"); 300MODULE_ALIAS("platform:da9062-thermal");