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(&notificationList);
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(&notificationList);
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(&notificationList, 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(&notificationList, &notifyObject->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(&notificationList, &notifyObject->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(&notificationList, &notifyObject->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(&notificationList, &(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