blob: 01b0cb99445778513b67506a5cd7654909f2988c [file] [log] [blame]
Steve Twiss608567a2017-03-28 15:43:33 +01001/*
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);
Geert Uytterhoeven0a76f5c2018-10-12 09:20:17 +0200109 queue_delayed_work(system_freezable_wq, &thermal->work, delay);
Steve Twiss608567a2017-03-28 15:43:33 +0100110 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);
Geert Uytterhoeven0a76f5c2018-10-12 09:20:17 +0200128 queue_delayed_work(system_freezable_wq, &thermal->work, 0);
Steve Twiss608567a2017-03-28 15:43:33 +0100129
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");