Home | History | Annotate | Download | only in synaptics_s3708
      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 
    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 
    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 
    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 
    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.
    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.
    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().
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    398 static bool callbackFirmwareUpload(void *cookie)
    399 {
    400     return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0);
    401 }
    402 
    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 
    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 
    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 
    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 
    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 
    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 
    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