1 /*
2 * Copyright (c) 2020, Texas Instruments Incorporated
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * * Neither the name of Texas Instruments Incorporated nor the names of
17 * its contributors may be used to endorse or promote products derived
18 * from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
22 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
27 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
29 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 /* Includes */
34 #include <stdint.h>
35 #include <stdbool.h>
36 #include <string.h>
37
38 #include <ti/drivers/dpl/HwiP.h>
39
40 #include <ti/drivers/Temperature.h>
41 #include <ti/drivers/temperature/TemperatureCC26X2.h>
42
43 #include <ti/devices/DeviceFamily.h>
44 #include DeviceFamily_constructPath(inc/hw_memmap.h)
45 #include DeviceFamily_constructPath(inc/hw_ints.h)
46 #include DeviceFamily_constructPath(inc/hw_types.h)
47 #include DeviceFamily_constructPath(driverlib/cpu.h)
48 #include DeviceFamily_constructPath(driverlib/interrupt.h)
49 #include DeviceFamily_constructPath(driverlib/sys_ctrl.h)
50 #include DeviceFamily_constructPath(driverlib/aon_batmon.h)
51 #include DeviceFamily_constructPath(driverlib/aon_event.h)
52
53 /* Macros */
54 #define MIN(x,y) (((x) < (y)) ? (x) : (y))
55 #define MAX(x,y) (((x) > (y)) ? (x) : (y))
56
57 /* Defines */
58 #define BATMON_TEMPERATURE_MAX (250)
59 #define BATMON_TEMPERATURE_MIN (-250)
60 #define BATMON_TEMPERATURE_CODE_MAX 0x0000FF00
61 #define BATMON_TEMPERATURE_CODE_MIN 0x00010000
62 #define BATMON_TEMPERATURE_MASK_POSITIVE 0x0000FF00
63 #define BATMON_TEMPERATURE_MASK_NEGATIVE 0x0001FF00
64
65 /* Offset to apply to all thresholds before programming the hardware.
66 *
67 * When the hardware samples the temperature sensor, the returned reading
68 * is drawn from a probability distribution of measurements for each true
69 * temperature. In order for a notification to trigger, two separate
70 * measurements must cross the configured threshold temperature.
71 * The first is initiated by the hardware in the background to check whether
72 * to trigger the HWI. The second is triggered by software within the HWI
73 * function. If the first measurement is part of the long tail of the
74 * distribution, it is highly probable that the second measurement will not
75 * cross the threshold. This is effectively a spurrious interrupt that wastes
76 * energy and CPU cycles.
77 * If we program the actual hardware registers with an additional offset, we
78 * effectively shift the distribution up or down such that the first measurement
79 * triggers on the long tail of the distribution that is DISTRIBUTION_OFFSET
80 * degrees away from the threshold provided by the application. The second
81 * measurement then has a much higher probability of crossing the threshold
82 * and triggering the notification and an update of the thresholds.
83 *
84 * The risk is of course that a particular chip has a compressed distribution
85 * where the long tail does not reach far enough to trigger the HWI with the
86 * offset applied.
87 * Additionally, the device does not sample nearly as frequently when in
88 * standby.
89 *
90 * Both of these risks result in a less accurate but overall more useful system.
91 * If the temperature keeps rising, both scenarios still cause a notification
92 * to trigger. Given that temperature changes are usually not instantaneous,
93 * this should be considered an acceptable risk.
94 */
95 #define DISTRIBUTION_OFFSET 2
96
97 #define INVALID_TEMPERATURE_MAX BATMON_TEMPERATURE_MAX
98 #define INVALID_TEMPERATURE_MIN BATMON_TEMPERATURE_MIN
99
100 /* Forward declarations */
101 static void walkNotifyList();
102 static void setNextThresholds();
103 static void temperatureHwiFxn(uintptr_t arg0);
104 static void updateThresholds(int16_t thresholdHigh, int16_t thresholdLow);
105 static uint32_t degreesToCode(int32_t temperatureDegreesC);
106 static void setTempLowerLimit(int16_t thresholdLow);
107 static void setTempUpperLimit(int16_t thresholdHigh);
108 static void enableTempLowerLimit();
109 static void enableTempUpperLimit();
110 static void disableTempLowerLimit();
111 static void disableTempUpperLimit();
112 static void clearEventFlags();
113
114 /* Globals */
115
116 /* Hwi struct for the shared batmon interrupt */
117 static HwiP_Struct batmonHwi;
118
119 /* Global list that stores all registered notifications */
120 static List_List notificationList;
121
122 /* Current threshold values. These should always reflect the state of the
123 * batmon registers without the need to read them out, shift down, and sign
124 * extend the values.
125 */
126 static volatile int16_t currentThresholdHigh = INVALID_TEMPERATURE_MAX;
127 static volatile int16_t currentThresholdLow = INVALID_TEMPERATURE_MIN;
128
129 static bool isInitialized = 0;
130
131 extern const TemperatureCC26X2_Config TemperatureCC26X2_config;
132
133 /*
134 * ======== degreesToCode ========
135 */
degreesToCode(int32_t temperatureDegreesC)136 static uint32_t degreesToCode(int32_t temperatureDegreesC) {
137 /* Voltage dependent temp correction with 8 fractional bits */
138 int32_t correctionOffset;
139 /* Signed byte value representing the TEMP offset slope with battery
140 * voltage in degrees C/V with 4 fractional bits. This must be multiplied
141 * by the voltage delta between the current voltage and the voltage used
142 * to compute this slope.
143 */
144 int8_t voltageSlope;
145 int32_t temperatureCode;
146
147 /* Typecasting voltageSlope to int8_t prior to assignment in order to make
148 * sure sign extension works properly.
149 * Using byte read (HWREGB) in order to make more efficient code since
150 * voltageSlope is assigned to bits[7:0] of FCFG1_O_MISC_TRIM
151 */
152 voltageSlope = ((int8_t)HWREGB(FCFG1_BASE + FCFG1_O_MISC_TRIM));
153
154 /* Get the current supply voltage */
155 correctionOffset = ((int32_t)HWREG(AON_BATMON_BASE + AON_BATMON_O_BAT));
156 /* The voltageSlope is measured at 3V in production test. We need to remove
157 * this from the current voltage to find the delta we need to apply.
158 * At 3V, there should be no adjustment necessary. The BATMON voltage
159 * measurement has 8 fractional bits.
160 */
161 correctionOffset = correctionOffset - (3 << 8);
162 /* Multiply the delta with the voltageSlope. */
163 correctionOffset = correctionOffset * voltageSlope;
164 /* Right shift by four to remove the fractional bits */
165 correctionOffset = correctionOffset >> 4;
166
167 /* Shift up and then back down to sign-extend the temperatureCode.
168 * Shift the temperature up by net 8 bit positions to move the integer part
169 * into bits 16:8. This is what the register expects and gives us 8
170 * fractional bits to work with as well for voltage compensation.
171 */
172 temperatureCode = (int32_t)((uint32_t)temperatureDegreesC << (32 - AON_BATMON_TEMP_INT_W));
173 temperatureCode = temperatureCode >> (32 - AON_BATMON_TEMP_INT_W - AON_BATMON_TEMP_INT_S);
174
175 /* 0x80 represents the rounding factor of half the previous net shift value */
176 temperatureCode = temperatureCode - 0x80;
177
178 temperatureCode = temperatureCode + correctionOffset;
179
180 if (temperatureDegreesC <= BATMON_TEMPERATURE_MIN) {
181 temperatureCode = BATMON_TEMPERATURE_CODE_MIN;
182 }
183 else if (temperatureDegreesC >= BATMON_TEMPERATURE_MAX) {
184 temperatureCode = BATMON_TEMPERATURE_CODE_MAX;
185 }
186
187 if (temperatureCode < 0) {
188 temperatureCode &= BATMON_TEMPERATURE_MASK_NEGATIVE;
189 }
190 else if (temperatureCode >= 0) {
191 temperatureCode &= BATMON_TEMPERATURE_MASK_POSITIVE;
192 }
193
194 return (uint32_t)(temperatureCode);
195 }
196
197 /*
198 * ======== setTempLowerLimit ========
199 */
setTempLowerLimit(int16_t thresholdLow)200 static void setTempLowerLimit(int16_t thresholdLow) {
201 uint32_t temperatureCode = degreesToCode(thresholdLow - DISTRIBUTION_OFFSET);
202
203 HWREG(AON_BATMON_BASE + AON_BATMON_O_TEMPLL) = temperatureCode;
204
205 currentThresholdLow = thresholdLow;
206 }
207
208 /*
209 * ======== setTempUpperLimit ========
210 */
setTempUpperLimit(int16_t thresholdHigh)211 static void setTempUpperLimit(int16_t thresholdHigh) {
212 uint32_t temperatureCode = degreesToCode(thresholdHigh + DISTRIBUTION_OFFSET);
213
214 HWREG(AON_BATMON_BASE + AON_BATMON_O_TEMPUL) = temperatureCode;
215
216 currentThresholdHigh = thresholdHigh;
217 }
218
219 /*
220 * ======== enableTempLowerLimit ========
221 */
enableTempLowerLimit()222 static void enableTempLowerLimit() {
223 HWREG(AON_BATMON_BASE + AON_BATMON_O_EVENTMASK) |= AON_BATMON_EVENTMASK_TEMP_BELOW_LL_MASK;
224 }
225
226 /*
227 * ======== enableTempUpperLimit ========
228 */
enableTempUpperLimit()229 static void enableTempUpperLimit() {
230 HWREG(AON_BATMON_BASE + AON_BATMON_O_EVENTMASK) |= AON_BATMON_EVENTMASK_TEMP_OVER_UL_MASK;
231 }
232
233 /*
234 * ======== disableTempLowerLimit ========
235 */
disableTempLowerLimit()236 static void disableTempLowerLimit() {
237 HWREG(AON_BATMON_BASE + AON_BATMON_O_EVENTMASK) &= ~AON_BATMON_EVENTMASK_TEMP_BELOW_LL_MASK;
238 }
239
240 /*
241 * ======== disableTempUpperLimit ========
242 */
disableTempUpperLimit()243 static void disableTempUpperLimit() {
244 HWREG(AON_BATMON_BASE + AON_BATMON_O_EVENTMASK) &= ~AON_BATMON_EVENTMASK_TEMP_OVER_UL_MASK;
245 }
246
247 /*
248 * ======== clearEventFlags ========
249 */
clearEventFlags()250 static void clearEventFlags() {
251 do {
252 HWREG(AON_BATMON_BASE + AON_BATMON_O_EVENT) &= AON_BATMON_EVENT_TEMP_BELOW_LL |
253 AON_BATMON_EVENT_TEMP_OVER_UL;
254 } while (HWREG(AON_BATMON_BASE + AON_BATMON_O_EVENT) &
255 (AON_BATMON_EVENT_TEMP_BELOW_LL | AON_BATMON_EVENT_TEMP_OVER_UL));
256 }
257
258 /*
259 * ======== setNextThresholds ========
260 */
setNextThresholds()261 static void setNextThresholds() {
262 List_Elem *notifyLink;
263 int16_t nextThresholdHigh = INVALID_TEMPERATURE_MAX;
264 int16_t nextThresholdLow = INVALID_TEMPERATURE_MIN;
265 uint32_t key;
266
267 key = HwiP_disable();
268
269 /* Starting with the head of the list, keep track of the smallest high
270 * threshold and largest low threshold.
271 */
272 notifyLink = List_head(¬ificationList);
273
274 while (notifyLink != NULL) {
275 Temperature_NotifyObj* notifyObject = (Temperature_NotifyObj *)notifyLink;
276
277 nextThresholdHigh = MIN(nextThresholdHigh,
278 notifyObject->thresholdHigh);
279 nextThresholdLow = MAX(nextThresholdLow,
280 notifyObject->thresholdLow);
281
282 notifyLink = List_next(notifyLink);
283 }
284
285 /* Now that we have found the next upper and lower thresholds, set them.
286 * These could be INVALID_TEMPERATURE_MAX and/or INVALID_TEMPERATURE_MIN
287 * if the list is empty or only high/low notifications were registered.
288 */
289 updateThresholds(nextThresholdHigh, nextThresholdLow);
290
291 HwiP_restore(key);
292 }
293
294 /*
295 * ======== walkNotifyList ========
296 */
walkNotifyList()297 static void walkNotifyList() {
298 List_Elem *notifyLink = List_head(¬ificationList);
299 int16_t currentTemperature = Temperature_getTemperature();
300
301 /* If the notification list is empty, the head pointer will be
302 * NULL and the while loop will never execute the statement.
303 */
304 while (notifyLink != NULL) {
305 Temperature_NotifyObj* notifyObject = (Temperature_NotifyObj *)notifyLink;
306
307 /* Buffer the next link in case the notification triggers.
308 * Without buffering, we might skip list entries if the
309 * notifyObject is freed or reregistered and the notifyObject->link.next
310 * pointer is altered.
311 */
312 List_Elem *notifyLinkNext = List_next(notifyLink);
313
314 /* If the current temperature is below this notification's low
315 * threshold or above its high threshold, remove it from the list and
316 * call the callback fxn
317 */
318 if (currentTemperature <= notifyObject->thresholdLow ||
319 currentTemperature >= notifyObject->thresholdHigh) {
320
321 /* Choose the threshold to provide to the notifyFxn based on the
322 * thresholds and the current temperature.
323 */
324 int16_t threshold = (currentTemperature <= notifyObject->thresholdLow) ?
325 notifyObject->thresholdLow : notifyObject->thresholdHigh;
326
327 List_remove(¬ificationList, notifyLink);
328 notifyObject->isRegistered = false;
329
330 notifyObject->notifyFxn(currentTemperature,
331 threshold,
332 notifyObject->clientArg,
333 notifyObject);
334 }
335
336 notifyLink = notifyLinkNext;
337 }
338 }
339
340 /*
341 * ======== updateThresholds ========
342 */
updateThresholds(int16_t thresholdHigh,int16_t thresholdLow)343 static void updateThresholds(int16_t thresholdHigh, int16_t thresholdLow) {
344 if (thresholdHigh < currentThresholdHigh) {
345 setTempUpperLimit(thresholdHigh);
346 enableTempUpperLimit();
347 }
348
349 if (thresholdLow > currentThresholdLow) {
350 setTempLowerLimit(thresholdLow);
351 enableTempLowerLimit();
352 }
353 }
354
355 /*
356 * ======== temperatureHwiFxn ========
357 *
358 * Batmon interrupt triggered on high or low temperature event
359 */
temperatureHwiFxn(uintptr_t arg0)360 static void temperatureHwiFxn(uintptr_t arg0) {
361
362 setTempUpperLimit(INVALID_TEMPERATURE_MAX);
363 disableTempUpperLimit();
364
365 setTempLowerLimit(INVALID_TEMPERATURE_MIN);
366 disableTempLowerLimit();
367
368
369 /* Walk the notification list and issue any callbacks that have triggered
370 * at the current temperature.
371 */
372 walkNotifyList();
373
374 /* Walk the queue another time to find and set the next set of thresholds.
375 * This is faster than making even one extra access to AON_BATMON.
376 */
377 setNextThresholds();
378
379
380
381 /* Clear event flags. They may not immediately clear properly. */
382 clearEventFlags();
383
384 IntPendClear(INT_BATMON_COMB);
385 }
386
387 /*
388 * ======== Temperature_init ========
389 */
Temperature_init()390 void Temperature_init() {
391 uint32_t key;
392
393 key = HwiP_disable();
394
395 if (isInitialized == false) {
396 /* Initialise the batmon hwi. The temperature sensor shares this
397 * interrupt with the battery voltage monitoring events.
398 */
399 HwiP_Params hwiParams;
400 HwiP_Params_init(&hwiParams);
401 hwiParams.priority = TemperatureCC26X2_config.intPriority;
402 hwiParams.enableInt = true;
403 HwiP_construct(&batmonHwi, INT_BATMON_COMB, temperatureHwiFxn, NULL);
404
405 disableTempUpperLimit();
406 disableTempLowerLimit();
407
408 AONBatMonEnable();
409
410 /* Set the combined BATMON interrupt as a wakeup source. This means the
411 * BATMON can bring the device out of standby when an event is
412 * triggered.
413 * We use WU2 since WU0 is the RTC and WU1 is a pad (GPIO) event.
414 */
415 AONEventMcuWakeUpSet(AON_EVENT_MCU_WU2, AON_EVENT_BATMON_COMBINED);
416
417 isInitialized = true;
418 }
419
420 HwiP_restore(key);
421 }
422
423 /*
424 * ======== Temperature_getTemperature ========
425 */
Temperature_getTemperature(void)426 int16_t Temperature_getTemperature(void) {
427 /* The temperature on CC13X2/CC26X2 is stored in a 32-bit register
428 * containing a 9-bit signed integer part and a 2-bit unsigned fractional
429 * part.
430 * The driverlib fxn handles this as well as compensating for the battery
431 * voltage which also affects the measured temperature.
432 */
433 int16_t currentTemperature = AONBatMonTemperatureGetDegC();
434
435 return currentTemperature;
436 }
437
438 /*
439 * ======== Temperature_registerNotifyHigh ========
440 */
Temperature_registerNotifyHigh(Temperature_NotifyObj * notifyObject,int16_t thresholdHigh,Temperature_NotifyFxn notifyFxn,uintptr_t clientArg)441 int_fast16_t Temperature_registerNotifyHigh(Temperature_NotifyObj *notifyObject,
442 int16_t thresholdHigh,
443 Temperature_NotifyFxn notifyFxn,
444 uintptr_t clientArg) {
445 uint32_t key;
446
447 key = HwiP_disable();
448
449 notifyObject->thresholdHigh = thresholdHigh;
450 notifyObject->thresholdLow = INVALID_TEMPERATURE_MIN;
451 notifyObject->notifyFxn = notifyFxn;
452 notifyObject->clientArg = clientArg;
453
454 if (notifyObject->isRegistered == false) {
455 /* Add the notification to the end of the list.
456 * There is the implicit assumption that the notification is not already
457 * in the list. Otherwise the list linkage will be corrupted.
458 */
459 List_put(¬ificationList, ¬ifyObject->link);
460
461 notifyObject->isRegistered = true;
462 }
463
464 updateThresholds(notifyObject->thresholdHigh, notifyObject->thresholdLow);
465
466 HwiP_restore(key);
467
468 return Temperature_STATUS_SUCCESS;
469 }
470
471 /*
472 * ======== Temperature_registerNotifyLow ========
473 */
Temperature_registerNotifyLow(Temperature_NotifyObj * notifyObject,int16_t thresholdLow,Temperature_NotifyFxn notifyFxn,uintptr_t clientArg)474 int_fast16_t Temperature_registerNotifyLow(Temperature_NotifyObj *notifyObject,
475 int16_t thresholdLow,
476 Temperature_NotifyFxn notifyFxn,
477 uintptr_t clientArg){
478 uint32_t key;
479
480 key = HwiP_disable();
481
482 notifyObject->thresholdHigh = INVALID_TEMPERATURE_MAX;
483 notifyObject->thresholdLow = thresholdLow;
484 notifyObject->notifyFxn = notifyFxn;
485 notifyObject->clientArg = clientArg;
486
487 if (notifyObject->isRegistered == false) {
488 /* Add the notification to the end of the list.
489 * There is the implicit assumption that the notification is not already
490 * in the list. Otherwise the list linkage will be corrupted.
491 */
492 List_put(¬ificationList, ¬ifyObject->link);
493
494 notifyObject->isRegistered = true;
495 }
496
497 updateThresholds(notifyObject->thresholdHigh, notifyObject->thresholdLow);
498
499 HwiP_restore(key);
500
501 return Temperature_STATUS_SUCCESS;
502 }
503
504 /*
505 * ======== Temperature_registerNotifyRange ========
506 */
Temperature_registerNotifyRange(Temperature_NotifyObj * notifyObject,int16_t thresholdHigh,int16_t thresholdLow,Temperature_NotifyFxn notifyFxn,uintptr_t clientArg)507 int_fast16_t Temperature_registerNotifyRange(Temperature_NotifyObj *notifyObject,
508 int16_t thresholdHigh,
509 int16_t thresholdLow,
510 Temperature_NotifyFxn notifyFxn,
511 uintptr_t clientArg) {
512 uint32_t key;
513
514 key = HwiP_disable();
515
516 notifyObject->thresholdHigh = thresholdHigh;
517 notifyObject->thresholdLow = thresholdLow;
518 notifyObject->notifyFxn = notifyFxn;
519 notifyObject->clientArg = clientArg;
520
521 if (notifyObject->isRegistered == false) {
522 /* Add the notification to the end of the list.
523 * There is the implicit assumption that the notification is not already
524 * in the list. Otherwise the list linkage will be corrupted.
525 */
526 List_put(¬ificationList, ¬ifyObject->link);
527
528 notifyObject->isRegistered = true;
529 }
530
531 updateThresholds(notifyObject->thresholdHigh, notifyObject->thresholdLow);
532
533 HwiP_restore(key);
534
535 return Temperature_STATUS_SUCCESS;
536 }
537
538 /*
539 * ======== Temperature_unregisterNotify ========
540 */
Temperature_unregisterNotify(Temperature_NotifyObj * notifyObject)541 int_fast16_t Temperature_unregisterNotify(Temperature_NotifyObj *notifyObject) {
542 uint32_t key;
543
544 key = HwiP_disable();
545
546 if (notifyObject->isRegistered == true) {
547 /* Remove the notification from the list */
548 List_remove(¬ificationList, &(notifyObject->link));
549
550 notifyObject->isRegistered = false;
551 }
552
553 /* Find the next set of thresholds and update the registers */
554 setNextThresholds();
555
556 HwiP_restore(key);
557
558 return Temperature_STATUS_SUCCESS;
559 }
560