1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <errno.h>
18 #include <float.h>
19 #include <stdlib.h>
20 #include <string.h>
21 
22 #include <eventnums.h>
23 #include <gpio.h>
24 #include <heap.h>
25 #include <hostIntf.h>
26 #include <isr.h>
27 #include <i2c.h>
28 #include <nanohubPacket.h>
29 #include <sensors.h>
30 #include <seos.h>
31 #include <timer.h>
32 #include <util.h>
33 
34 #include <cpu/cpuMath.h>
35 
36 #include <plat/exti.h>
37 #include <plat/gpio.h>
38 #include <plat/syscfg.h>
39 
40 #define S3708_APP_ID                APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 13)
41 #define S3708_APP_VERSION           1
42 
43 #define I2C_BUS_ID                  0
44 #define I2C_SPEED                   400000
45 #define I2C_ADDR                    0x20
46 
47 #define S3708_REG_PAGE_SELECT       0xFF
48 
49 #define S3708_REG_F01_DATA_BASE     0x06
50 #define S3708_INT_STATUS_LPWG       0x04
51 
52 #define S3708_REG_DATA_BASE         0x08
53 #define S3708_REG_DATA_4_OFFSET     0x02
54 #define S3708_INT_STATUS_DOUBLE_TAP 0x03
55 
56 #define S3708_REG_F01_CTRL_BASE     0x14
57 #define S3708_NORMAL_MODE           0x00
58 #define S3708_SLEEP_MODE            0x01
59 
60 #define S3708_REG_CTRL_BASE         0x1b
61 #define S3708_REG_CTRL_20_OFFSET    0x07
62 #define S3708_REPORT_MODE_CONT      0x00
63 #define S3708_REPORT_MODE_LPWG      0x02
64 
65 #define MAX_PENDING_I2C_REQUESTS    4
66 #define MAX_I2C_TRANSFER_SIZE       8
67 #define MAX_I2C_RETRY_DELAY         250000000ull // 250 milliseconds
68 #define MAX_I2C_RETRY_COUNT         (15000000000ull / MAX_I2C_RETRY_DELAY) // 15 seconds
69 #define HACK_RETRY_SKIP_COUNT       1
70 
71 #define DEFAULT_PROX_RATE_HZ        SENSOR_HZ(5.0f)
72 #define DEFAULT_PROX_LATENCY        0.0
73 #define PROXIMITY_THRESH_NEAR       5.0f    // distance in cm
74 
75 #define EVT_SENSOR_PROX  sensorGetMyEventType(SENS_TYPE_PROX)
76 
77 #define ENABLE_DEBUG 0
78 
79 #define VERBOSE_PRINT(fmt, ...) osLog(LOG_VERBOSE, "[DoubleTouch] " fmt, ##__VA_ARGS__)
80 #define INFO_PRINT(fmt, ...) osLog(LOG_INFO, "[DoubleTouch] " fmt, ##__VA_ARGS__)
81 #define ERROR_PRINT(fmt, ...) osLog(LOG_ERROR, "[DoubleTouch] " fmt, ##__VA_ARGS__)
82 #if ENABLE_DEBUG
83 #define DEBUG_PRINT(fmt, ...)  osLog(LOG_DEBUG, "[DoubleTouch] " fmt, ##__VA_ARGS__)
84 #else
85 #define DEBUG_PRINT(fmt, ...) ((void)0)
86 #endif
87 
88 
89 #ifndef TOUCH_PIN
90 #error "TOUCH_PIN is not defined; please define in variant.h"
91 #endif
92 
93 #ifndef TOUCH_IRQ
94 #error "TOUCH_IRQ is not defined; please define in variant.h"
95 #endif
96 
97 enum SensorEvents
98 {
99     EVT_SENSOR_I2C = EVT_APP_START + 1,
100     EVT_SENSOR_TOUCH_INTERRUPT,
101     EVT_SENSOR_RETRY_TIMER,
102 };
103 
104 enum TaskState
105 {
106     STATE_ENABLE_0,
107     STATE_ENABLE_1,
108     STATE_ENABLE_2,
109     STATE_DISABLE_0,
110     STATE_INT_HANDLE_0,
111     STATE_INT_HANDLE_1,
112     STATE_IDLE,
113     STATE_CANCELLED,
114 };
115 
116 struct I2cTransfer
117 {
118     size_t tx;
119     size_t rx;
120     int err;
121     uint8_t txrxBuf[MAX_I2C_TRANSFER_SIZE];
122     uint8_t state;
123     bool inUse;
124 };
125 
126 struct TaskStatistics {
127     uint64_t enabledTimestamp;
128     uint64_t proxEnabledTimestamp;
129     uint64_t lastProxFarTimestamp;
130     uint64_t totalEnabledTime;
131     uint64_t totalProxEnabledTime;
132     uint64_t totalProxFarTime;
133     uint32_t totalProxBecomesFar;
134     uint32_t totalProxBecomesNear;
135 };
136 
137 enum ProxState {
138     PROX_STATE_UNKNOWN,
139     PROX_STATE_NEAR,
140     PROX_STATE_FAR
141 };
142 
143 static struct TaskStruct
144 {
145     struct Gpio *pin;
146     struct ChainedIsr isr;
147     struct TaskStatistics stats;
148     struct I2cTransfer transfers[MAX_PENDING_I2C_REQUESTS];
149     uint32_t id;
150     uint32_t handle;
151     uint32_t retryTimerHandle;
152     uint32_t retryCnt;
153     uint32_t proxHandle;
154     enum ProxState proxState;
155     bool on;
156     bool gestureEnabled;
157     bool isrEnabled;
158 } mTask;
159 
enableInterrupt(bool enable)160 static inline void enableInterrupt(bool enable)
161 {
162     if (!mTask.isrEnabled && enable) {
163         extiEnableIntGpio(mTask.pin, EXTI_TRIGGER_FALLING);
164         extiChainIsr(TOUCH_IRQ, &mTask.isr);
165     } else if (mTask.isrEnabled && !enable) {
166         extiUnchainIsr(TOUCH_IRQ, &mTask.isr);
167         extiDisableIntGpio(mTask.pin);
168     }
169     mTask.isrEnabled = enable;
170 }
171 
touchIsr(struct ChainedIsr * localIsr)172 static bool touchIsr(struct ChainedIsr *localIsr)
173 {
174     struct TaskStruct *data = container_of(localIsr, struct TaskStruct, isr);
175 
176     if (!extiIsPendingGpio(data->pin)) {
177         return false;
178     }
179 
180     osEnqueuePrivateEvt(EVT_SENSOR_TOUCH_INTERRUPT, NULL, NULL, data->id);
181 
182     extiClearPendingGpio(data->pin);
183 
184     return true;
185 }
186 
i2cCallback(void * cookie,size_t tx,size_t rx,int err)187 static void i2cCallback(void *cookie, size_t tx, size_t rx, int err)
188 {
189     struct I2cTransfer *xfer = cookie;
190 
191     xfer->tx = tx;
192     xfer->rx = rx;
193     xfer->err = err;
194 
195     osEnqueuePrivateEvt(EVT_SENSOR_I2C, cookie, NULL, mTask.id);
196     // Do not print error for ENXIO since we expect there to be times where we
197     // cannot talk to the touch controller.
198     if (err == -ENXIO) {
199         DEBUG_PRINT("i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err);
200     } else if (err != 0) {
201         ERROR_PRINT("i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err);
202     }
203 }
204 
retryTimerCallback(uint32_t timerId,void * cookie)205 static void retryTimerCallback(uint32_t timerId, void *cookie)
206 {
207     osEnqueuePrivateEvt(EVT_SENSOR_RETRY_TIMER, cookie, NULL, mTask.id);
208 }
209 
210 // Allocate a buffer and mark it as in use with the given state, or return NULL
211 // if no buffers available. Must *not* be called from interrupt context.
allocXfer(uint8_t state)212 static struct I2cTransfer *allocXfer(uint8_t state)
213 {
214     size_t i;
215 
216     for (i = 0; i < ARRAY_SIZE(mTask.transfers); i++) {
217         if (!mTask.transfers[i].inUse) {
218             mTask.transfers[i].inUse = true;
219             mTask.transfers[i].state = state;
220             memset(mTask.transfers[i].txrxBuf, 0x00, sizeof(mTask.transfers[i].txrxBuf));
221             return &mTask.transfers[i];
222         }
223     }
224 
225     ERROR_PRINT("Ran out of I2C buffers!");
226     return NULL;
227 }
228 
229 // Helper function to initiate the I2C transfer. Returns true is the transaction
230 // was successfully register by I2C driver. Otherwise, returns false.
performXfer(struct I2cTransfer * xfer,size_t txBytes,size_t rxBytes)231 static bool performXfer(struct I2cTransfer *xfer, size_t txBytes, size_t rxBytes)
232 {
233     int ret;
234 
235     if ((txBytes > MAX_I2C_TRANSFER_SIZE) || (rxBytes > MAX_I2C_TRANSFER_SIZE)) {
236         ERROR_PRINT("txBytes and rxBytes must be less than %d", MAX_I2C_TRANSFER_SIZE);
237         return false;
238     }
239 
240     if (rxBytes) {
241         ret = i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, txBytes, xfer->txrxBuf, rxBytes, i2cCallback, xfer);
242     } else {
243         ret = i2cMasterTx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, txBytes, i2cCallback, xfer);
244     }
245 
246     if (ret != 0) {
247         ERROR_PRINT("I2C transfer was not successful (error %d)!", ret);
248     }
249 
250     return (ret == 0);
251 }
252 
253 // Helper function to write a one byte register. Returns true if we got a
254 // successful return value from i2cMasterTx().
writeRegister(uint8_t reg,uint8_t value,uint8_t state)255 static bool writeRegister(uint8_t reg, uint8_t value, uint8_t state)
256 {
257     struct I2cTransfer *xfer = allocXfer(state);
258 
259     if (xfer != NULL) {
260         xfer->txrxBuf[0] = reg;
261         xfer->txrxBuf[1] = value;
262         return performXfer(xfer, 2, 0);
263     }
264 
265     return false;
266 }
267 
setSleepEnable(bool enable,uint8_t state)268 static bool setSleepEnable(bool enable, uint8_t state)
269 {
270     return writeRegister(S3708_REG_F01_CTRL_BASE, enable ? S3708_SLEEP_MODE : S3708_NORMAL_MODE, state);
271 }
272 
setReportingMode(uint8_t mode,uint8_t state)273 static bool setReportingMode(uint8_t mode, uint8_t state)
274 {
275     struct I2cTransfer *xfer;
276 
277     xfer = allocXfer(state);
278     if (xfer != NULL) {
279         xfer->txrxBuf[0] = S3708_REG_CTRL_BASE + S3708_REG_CTRL_20_OFFSET;
280         xfer->txrxBuf[1] = 0x00;
281         xfer->txrxBuf[2] = 0x00;
282         xfer->txrxBuf[3] = mode;
283         return performXfer(xfer, 4, 0);
284     }
285 
286     return false;
287 }
288 
setRetryTimer()289 static void setRetryTimer()
290 {
291     mTask.retryCnt++;
292     if (mTask.retryCnt < MAX_I2C_RETRY_COUNT) {
293         mTask.retryTimerHandle = timTimerSet(MAX_I2C_RETRY_DELAY, 0, 50, retryTimerCallback, NULL, true);
294         if (!mTask.retryTimerHandle) {
295             ERROR_PRINT("failed to allocate timer");
296         }
297     } else {
298         ERROR_PRINT("could not communicate with touch controller");
299     }
300 }
301 
setGesturePower(bool enable,bool skipI2c)302 static void setGesturePower(bool enable, bool skipI2c)
303 {
304     bool ret;
305     size_t i;
306 
307     VERBOSE_PRINT("gesture: %d", enable);
308 
309     // Cancel any pending I2C transactions by changing the callback state
310     for (i = 0; i < ARRAY_SIZE(mTask.transfers); i++) {
311         if (mTask.transfers[i].inUse) {
312             mTask.transfers[i].state = STATE_CANCELLED;
313         }
314     }
315 
316     if (enable) {
317         mTask.retryCnt = 0;
318 
319         // Set page number to 0x00
320         ret = writeRegister(S3708_REG_PAGE_SELECT, 0x00, STATE_ENABLE_0);
321     } else {
322         // Cancel any pending retries
323         if (mTask.retryTimerHandle) {
324             timTimerCancel(mTask.retryTimerHandle);
325             mTask.retryTimerHandle = 0;
326         }
327 
328         if (skipI2c) {
329             ret = true;
330         } else {
331             // Reset to continuous reporting mode
332             ret = setReportingMode(S3708_REPORT_MODE_CONT, STATE_DISABLE_0);
333         }
334     }
335 
336     if (ret) {
337         mTask.gestureEnabled = enable;
338         enableInterrupt(enable);
339     }
340 }
341 
configProx(bool on)342 static void configProx(bool on) {
343     if (on) {
344         mTask.stats.proxEnabledTimestamp = sensorGetTime();
345         sensorRequest(mTask.id, mTask.proxHandle, DEFAULT_PROX_RATE_HZ,
346                       DEFAULT_PROX_LATENCY);
347         osEventSubscribe(mTask.id, EVT_SENSOR_PROX);
348     } else {
349         sensorRelease(mTask.id, mTask.proxHandle);
350         osEventUnsubscribe(mTask.id, EVT_SENSOR_PROX);
351 
352         mTask.stats.totalProxEnabledTime += sensorGetTime() - mTask.stats.proxEnabledTimestamp;
353         if (mTask.proxState == PROX_STATE_FAR) {
354             mTask.stats.totalProxFarTime += sensorGetTime() - mTask.stats.lastProxFarTimestamp;
355         }
356     }
357     mTask.proxState = PROX_STATE_UNKNOWN;
358 }
359 
callbackPower(bool on,void * cookie)360 static bool callbackPower(bool on, void *cookie)
361 {
362     uint32_t enabledSeconds, proxEnabledSeconds, proxFarSeconds;
363 
364     VERBOSE_PRINT("power: %d", on);
365 
366     if (on) {
367         mTask.stats.enabledTimestamp = sensorGetTime();
368     } else {
369         mTask.stats.totalEnabledTime += sensorGetTime() - mTask.stats.enabledTimestamp;
370     }
371 
372     enabledSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalEnabledTime, 1000000000);
373     proxEnabledSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalProxEnabledTime, 1000000000);
374     proxFarSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalProxFarTime, 1000000000);
375     VERBOSE_PRINT("STATS: enabled %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
376                ", prox enabled %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
377                ", prox far %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32
378                ", prox *->f %" PRIu32
379                ", prox *->n %" PRIu32,
380         enabledSeconds / 3600, (enabledSeconds % 3600) / 60, enabledSeconds % 60,
381         proxEnabledSeconds / 3600, (proxEnabledSeconds % 3600) / 60, proxEnabledSeconds % 60,
382         proxFarSeconds / 3600, (proxFarSeconds % 3600) / 60, proxFarSeconds % 60,
383         mTask.stats.totalProxBecomesFar,
384         mTask.stats.totalProxBecomesNear);
385 
386     // If the task is disabled, that means the AP is on and has switched the I2C
387     // mux. Therefore, no I2C transactions will succeed so skip them.
388     if (mTask.gestureEnabled) {
389         setGesturePower(false, true /* skipI2c */);
390     }
391 
392     mTask.on = on;
393     configProx(on);
394 
395     return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, mTask.on, 0);
396 }
397 
callbackFirmwareUpload(void * cookie)398 static bool callbackFirmwareUpload(void *cookie)
399 {
400     return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0);
401 }
402 
callbackSetRate(uint32_t rate,uint64_t latency,void * cookie)403 static bool callbackSetRate(uint32_t rate, uint64_t latency, void *cookie)
404 {
405     return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);
406 }
407 
callbackFlush(void * cookie)408 static bool callbackFlush(void *cookie)
409 {
410     return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_DOUBLE_TOUCH), SENSOR_DATA_EVENT_FLUSH, NULL);
411 }
412 
413 static const struct SensorInfo mSensorInfo = {
414     .sensorName = "Double Touch",
415     .sensorType = SENS_TYPE_DOUBLE_TOUCH,
416     .numAxis = NUM_AXIS_EMBEDDED,
417     .interrupt = NANOHUB_INT_WAKEUP,
418     .minSamples = 20
419 };
420 
421 static const struct SensorOps mSensorOps =
422 {
423     .sensorPower = callbackPower,
424     .sensorFirmwareUpload = callbackFirmwareUpload,
425     .sensorSetRate = callbackSetRate,
426     .sensorFlush = callbackFlush,
427 };
428 
processI2cResponse(struct I2cTransfer * xfer)429 static void processI2cResponse(struct I2cTransfer *xfer)
430 {
431     struct I2cTransfer *nextXfer;
432     union EmbeddedDataPoint sample;
433 
434     switch (xfer->state) {
435         case STATE_ENABLE_0:
436             setSleepEnable(false, STATE_ENABLE_1);
437             break;
438 
439         case STATE_ENABLE_1:
440             // HACK: DozeService reactivates pickup gesture before the screen
441             // comes on, so we need to wait for some time after enabling before
442             // trying to talk to touch controller. We may see the touch
443             // controller on the first few samples and then have communication
444             // switched off. So, wait HACK_RETRY_SKIP_COUNT samples before we
445             // consider the transaction.
446             if (mTask.retryCnt < HACK_RETRY_SKIP_COUNT) {
447                 setRetryTimer();
448             } else {
449                 setReportingMode(S3708_REPORT_MODE_LPWG, STATE_ENABLE_2);
450             }
451             break;
452 
453         case STATE_ENABLE_2:
454             // Poll the GPIO line to see if it is low/active (it might have been
455             // low when we enabled the ISR, e.g. due to a pending touch event).
456             // Only do this after arming the LPWG, so it happens after we know
457             // that we can talk to the touch controller.
458             if (!gpioGet(mTask.pin)) {
459                 osEnqueuePrivateEvt(EVT_SENSOR_TOUCH_INTERRUPT, NULL, NULL, mTask.id);
460             }
461             break;
462 
463         case STATE_DISABLE_0:
464             setSleepEnable(true, STATE_IDLE);
465             break;
466 
467         case STATE_INT_HANDLE_0:
468             // If the interrupt was from the LPWG function, read the function interrupt status register
469             if (xfer->txrxBuf[1] & S3708_INT_STATUS_LPWG) {
470                 nextXfer = allocXfer(STATE_INT_HANDLE_1);
471                 if (nextXfer != NULL) {
472                     nextXfer->txrxBuf[0] = S3708_REG_DATA_BASE + S3708_REG_DATA_4_OFFSET;
473                     performXfer(nextXfer, 1, 5);
474                 }
475             }
476             break;
477 
478         case STATE_INT_HANDLE_1:
479             // Verify the LPWG interrupt status
480             if (xfer->txrxBuf[0] & S3708_INT_STATUS_DOUBLE_TAP) {
481                 DEBUG_PRINT("Sending event");
482                 sample.idata = 1;
483                 osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_DOUBLE_TOUCH), sample.vptr, NULL);
484             }
485             break;
486 
487         default:
488             break;
489     }
490 }
491 
handleI2cEvent(struct I2cTransfer * xfer)492 static void handleI2cEvent(struct I2cTransfer *xfer)
493 {
494     if (xfer->err == 0) {
495         processI2cResponse(xfer);
496     } else if (xfer->state == STATE_ENABLE_0 || xfer->state == STATE_ENABLE_1) {
497         setRetryTimer();
498     }
499 
500     xfer->inUse = false;
501 }
502 
handleEvent(uint32_t evtType,const void * evtData)503 static void handleEvent(uint32_t evtType, const void* evtData)
504 {
505     struct I2cTransfer *xfer;
506     union EmbeddedDataPoint embeddedSample;
507     enum ProxState lastProxState;
508     int ret;
509 
510     switch (evtType) {
511         case EVT_APP_START:
512             osEventUnsubscribe(mTask.id, EVT_APP_START);
513             ret = i2cMasterRequest(I2C_BUS_ID, I2C_SPEED);
514             // Since the i2c bus can be shared with other drivers, it is
515             // possible that one of the other drivers requested the bus first.
516             // Therefore, either 0 or -EBUSY is an acceptable return.
517             if ((ret < 0) && (ret != -EBUSY)) {
518                 ERROR_PRINT("i2cMasterRequest() failed!");
519             }
520 
521             sensorFind(SENS_TYPE_PROX, 0, &mTask.proxHandle);
522 
523             sensorRegisterInitComplete(mTask.handle);
524             break;
525 
526         case EVT_SENSOR_I2C:
527             handleI2cEvent((struct I2cTransfer *)evtData);
528             break;
529 
530         case EVT_SENSOR_TOUCH_INTERRUPT:
531             if (mTask.on) {
532                 // Read the interrupt status register
533                 xfer = allocXfer(STATE_INT_HANDLE_0);
534                 if (xfer != NULL) {
535                     xfer->txrxBuf[0] = S3708_REG_F01_DATA_BASE;
536                     performXfer(xfer, 1, 2);
537                 }
538             }
539             break;
540 
541         case EVT_SENSOR_PROX:
542             if (mTask.on) {
543                 // cast off the const, and cast to union
544                 embeddedSample = (union EmbeddedDataPoint)((void*)evtData);
545                 lastProxState = mTask.proxState;
546                 mTask.proxState = (embeddedSample.fdata < PROXIMITY_THRESH_NEAR) ? PROX_STATE_NEAR : PROX_STATE_FAR;
547 
548                 if ((lastProxState != PROX_STATE_FAR) && (mTask.proxState == PROX_STATE_FAR)) {
549                     ++mTask.stats.totalProxBecomesFar;
550                     mTask.stats.lastProxFarTimestamp = sensorGetTime();
551                     setGesturePower(true, false);
552                 } else if ((lastProxState != PROX_STATE_NEAR) && (mTask.proxState == PROX_STATE_NEAR)) {
553                     ++mTask.stats.totalProxBecomesNear;
554                     if (lastProxState == PROX_STATE_FAR) {
555                         mTask.stats.totalProxFarTime += sensorGetTime() - mTask.stats.lastProxFarTimestamp;
556                         setGesturePower(false, false);
557                     }
558                 }
559             }
560             break;
561 
562         case EVT_SENSOR_RETRY_TIMER:
563             if (mTask.on) {
564                 // Set page number to 0x00
565                 writeRegister(S3708_REG_PAGE_SELECT, 0x00, STATE_ENABLE_0);
566             }
567             break;
568     }
569 }
570 
startTask(uint32_t taskId)571 static bool startTask(uint32_t taskId)
572 {
573     mTask.id = taskId;
574     mTask.handle = sensorRegister(&mSensorInfo, &mSensorOps, NULL, false);
575 
576     mTask.pin = gpioRequest(TOUCH_PIN);
577     gpioConfigInput(mTask.pin, GPIO_SPEED_LOW, GPIO_PULL_NONE);
578     syscfgSetExtiPort(mTask.pin);
579     mTask.isr.func = touchIsr;
580 
581     mTask.stats.totalProxBecomesFar = 0;
582     mTask.stats.totalProxBecomesNear = 0;
583 
584     osEventSubscribe(taskId, EVT_APP_START);
585     return true;
586 }
587 
endTask(void)588 static void endTask(void)
589 {
590     enableInterrupt(false);
591     extiUnchainIsr(TOUCH_IRQ, &mTask.isr);
592     extiClearPendingGpio(mTask.pin);
593     gpioRelease(mTask.pin);
594 
595     i2cMasterRelease(I2C_BUS_ID);
596 
597     sensorUnregister(mTask.handle);
598 }
599 
600 INTERNAL_APP_INIT(S3708_APP_ID, S3708_APP_VERSION, startTask, endTask, handleEvent);
601