/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define S3708_APP_ID APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 13) #define S3708_APP_VERSION 1 #define I2C_BUS_ID 0 #define I2C_SPEED 400000 #define I2C_ADDR 0x20 #define S3708_REG_PAGE_SELECT 0xFF #define S3708_REG_F01_DATA_BASE 0x06 #define S3708_INT_STATUS_LPWG 0x04 #define S3708_REG_DATA_BASE 0x08 #define S3708_REG_DATA_4_OFFSET 0x02 #define S3708_INT_STATUS_DOUBLE_TAP 0x03 #define S3708_REG_F01_CTRL_BASE 0x14 #define S3708_NORMAL_MODE 0x00 #define S3708_SLEEP_MODE 0x01 #define S3708_REG_CTRL_BASE 0x1b #define S3708_REG_CTRL_20_OFFSET 0x07 #define S3708_REPORT_MODE_CONT 0x00 #define S3708_REPORT_MODE_LPWG 0x02 #define MAX_PENDING_I2C_REQUESTS 4 #define MAX_I2C_TRANSFER_SIZE 8 #define MAX_I2C_RETRY_DELAY 250000000ull // 250 milliseconds #define MAX_I2C_RETRY_COUNT (15000000000ull / MAX_I2C_RETRY_DELAY) // 15 seconds #define HACK_RETRY_SKIP_COUNT 1 #define DEFAULT_PROX_RATE_HZ SENSOR_HZ(5.0f) #define DEFAULT_PROX_LATENCY 0.0 #define PROXIMITY_THRESH_NEAR 5.0f // distance in cm #define EVT_SENSOR_PROX sensorGetMyEventType(SENS_TYPE_PROX) #define ENABLE_DEBUG 0 #define VERBOSE_PRINT(fmt, ...) osLog(LOG_VERBOSE, "[DoubleTouch] " fmt, ##__VA_ARGS__) #define INFO_PRINT(fmt, ...) osLog(LOG_INFO, "[DoubleTouch] " fmt, ##__VA_ARGS__) #define ERROR_PRINT(fmt, ...) osLog(LOG_ERROR, "[DoubleTouch] " fmt, ##__VA_ARGS__) #if ENABLE_DEBUG #define DEBUG_PRINT(fmt, ...) osLog(LOG_DEBUG, "[DoubleTouch] " fmt, ##__VA_ARGS__) #else #define DEBUG_PRINT(fmt, ...) ((void)0) #endif #ifndef TOUCH_PIN #error "TOUCH_PIN is not defined; please define in variant.h" #endif #ifndef TOUCH_IRQ #error "TOUCH_IRQ is not defined; please define in variant.h" #endif enum SensorEvents { EVT_SENSOR_I2C = EVT_APP_START + 1, EVT_SENSOR_TOUCH_INTERRUPT, EVT_SENSOR_RETRY_TIMER, }; enum TaskState { STATE_ENABLE_0, STATE_ENABLE_1, STATE_ENABLE_2, STATE_DISABLE_0, STATE_INT_HANDLE_0, STATE_INT_HANDLE_1, STATE_IDLE, STATE_CANCELLED, }; struct I2cTransfer { size_t tx; size_t rx; int err; uint8_t txrxBuf[MAX_I2C_TRANSFER_SIZE]; uint8_t state; bool inUse; }; struct TaskStatistics { uint64_t enabledTimestamp; uint64_t proxEnabledTimestamp; uint64_t lastProxFarTimestamp; uint64_t totalEnabledTime; uint64_t totalProxEnabledTime; uint64_t totalProxFarTime; uint32_t totalProxBecomesFar; uint32_t totalProxBecomesNear; }; enum ProxState { PROX_STATE_UNKNOWN, PROX_STATE_NEAR, PROX_STATE_FAR }; static struct TaskStruct { struct Gpio *pin; struct ChainedIsr isr; struct TaskStatistics stats; struct I2cTransfer transfers[MAX_PENDING_I2C_REQUESTS]; uint32_t id; uint32_t handle; uint32_t retryTimerHandle; uint32_t retryCnt; uint32_t proxHandle; enum ProxState proxState; bool on; bool gestureEnabled; bool isrEnabled; } mTask; static inline void enableInterrupt(bool enable) { if (!mTask.isrEnabled && enable) { extiEnableIntGpio(mTask.pin, EXTI_TRIGGER_FALLING); extiChainIsr(TOUCH_IRQ, &mTask.isr); } else if (mTask.isrEnabled && !enable) { extiUnchainIsr(TOUCH_IRQ, &mTask.isr); extiDisableIntGpio(mTask.pin); } mTask.isrEnabled = enable; } static bool touchIsr(struct ChainedIsr *localIsr) { struct TaskStruct *data = container_of(localIsr, struct TaskStruct, isr); if (!extiIsPendingGpio(data->pin)) { return false; } osEnqueuePrivateEvt(EVT_SENSOR_TOUCH_INTERRUPT, NULL, NULL, data->id); extiClearPendingGpio(data->pin); return true; } static void i2cCallback(void *cookie, size_t tx, size_t rx, int err) { struct I2cTransfer *xfer = cookie; xfer->tx = tx; xfer->rx = rx; xfer->err = err; osEnqueuePrivateEvt(EVT_SENSOR_I2C, cookie, NULL, mTask.id); // Do not print error for ENXIO since we expect there to be times where we // cannot talk to the touch controller. if (err == -ENXIO) { DEBUG_PRINT("i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err); } else if (err != 0) { ERROR_PRINT("i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err); } } static void retryTimerCallback(uint32_t timerId, void *cookie) { osEnqueuePrivateEvt(EVT_SENSOR_RETRY_TIMER, cookie, NULL, mTask.id); } // Allocate a buffer and mark it as in use with the given state, or return NULL // if no buffers available. Must *not* be called from interrupt context. static struct I2cTransfer *allocXfer(uint8_t state) { size_t i; for (i = 0; i < ARRAY_SIZE(mTask.transfers); i++) { if (!mTask.transfers[i].inUse) { mTask.transfers[i].inUse = true; mTask.transfers[i].state = state; memset(mTask.transfers[i].txrxBuf, 0x00, sizeof(mTask.transfers[i].txrxBuf)); return &mTask.transfers[i]; } } ERROR_PRINT("Ran out of I2C buffers!"); return NULL; } // Helper function to initiate the I2C transfer. Returns true is the transaction // was successfully register by I2C driver. Otherwise, returns false. static bool performXfer(struct I2cTransfer *xfer, size_t txBytes, size_t rxBytes) { int ret; if ((txBytes > MAX_I2C_TRANSFER_SIZE) || (rxBytes > MAX_I2C_TRANSFER_SIZE)) { ERROR_PRINT("txBytes and rxBytes must be less than %d", MAX_I2C_TRANSFER_SIZE); return false; } if (rxBytes) { ret = i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, txBytes, xfer->txrxBuf, rxBytes, i2cCallback, xfer); } else { ret = i2cMasterTx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, txBytes, i2cCallback, xfer); } if (ret != 0) { ERROR_PRINT("I2C transfer was not successful (error %d)!", ret); } return (ret == 0); } // Helper function to write a one byte register. Returns true if we got a // successful return value from i2cMasterTx(). static bool writeRegister(uint8_t reg, uint8_t value, uint8_t state) { struct I2cTransfer *xfer = allocXfer(state); if (xfer != NULL) { xfer->txrxBuf[0] = reg; xfer->txrxBuf[1] = value; return performXfer(xfer, 2, 0); } return false; } static bool setSleepEnable(bool enable, uint8_t state) { return writeRegister(S3708_REG_F01_CTRL_BASE, enable ? S3708_SLEEP_MODE : S3708_NORMAL_MODE, state); } static bool setReportingMode(uint8_t mode, uint8_t state) { struct I2cTransfer *xfer; xfer = allocXfer(state); if (xfer != NULL) { xfer->txrxBuf[0] = S3708_REG_CTRL_BASE + S3708_REG_CTRL_20_OFFSET; xfer->txrxBuf[1] = 0x00; xfer->txrxBuf[2] = 0x00; xfer->txrxBuf[3] = mode; return performXfer(xfer, 4, 0); } return false; } static void setRetryTimer() { mTask.retryCnt++; if (mTask.retryCnt < MAX_I2C_RETRY_COUNT) { mTask.retryTimerHandle = timTimerSet(MAX_I2C_RETRY_DELAY, 0, 50, retryTimerCallback, NULL, true); if (!mTask.retryTimerHandle) { ERROR_PRINT("failed to allocate timer"); } } else { ERROR_PRINT("could not communicate with touch controller"); } } static void setGesturePower(bool enable, bool skipI2c) { bool ret; size_t i; VERBOSE_PRINT("gesture: %d", enable); // Cancel any pending I2C transactions by changing the callback state for (i = 0; i < ARRAY_SIZE(mTask.transfers); i++) { if (mTask.transfers[i].inUse) { mTask.transfers[i].state = STATE_CANCELLED; } } if (enable) { mTask.retryCnt = 0; // Set page number to 0x00 ret = writeRegister(S3708_REG_PAGE_SELECT, 0x00, STATE_ENABLE_0); } else { // Cancel any pending retries if (mTask.retryTimerHandle) { timTimerCancel(mTask.retryTimerHandle); mTask.retryTimerHandle = 0; } if (skipI2c) { ret = true; } else { // Reset to continuous reporting mode ret = setReportingMode(S3708_REPORT_MODE_CONT, STATE_DISABLE_0); } } if (ret) { mTask.gestureEnabled = enable; enableInterrupt(enable); } } static void configProx(bool on) { if (on) { mTask.stats.proxEnabledTimestamp = sensorGetTime(); sensorRequest(mTask.id, mTask.proxHandle, DEFAULT_PROX_RATE_HZ, DEFAULT_PROX_LATENCY); osEventSubscribe(mTask.id, EVT_SENSOR_PROX); } else { sensorRelease(mTask.id, mTask.proxHandle); osEventUnsubscribe(mTask.id, EVT_SENSOR_PROX); mTask.stats.totalProxEnabledTime += sensorGetTime() - mTask.stats.proxEnabledTimestamp; if (mTask.proxState == PROX_STATE_FAR) { mTask.stats.totalProxFarTime += sensorGetTime() - mTask.stats.lastProxFarTimestamp; } } mTask.proxState = PROX_STATE_UNKNOWN; } static bool callbackPower(bool on, void *cookie) { uint32_t enabledSeconds, proxEnabledSeconds, proxFarSeconds; VERBOSE_PRINT("power: %d", on); if (on) { mTask.stats.enabledTimestamp = sensorGetTime(); } else { mTask.stats.totalEnabledTime += sensorGetTime() - mTask.stats.enabledTimestamp; } enabledSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalEnabledTime, 1000000000); proxEnabledSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalProxEnabledTime, 1000000000); proxFarSeconds = U64_DIV_BY_U64_CONSTANT(mTask.stats.totalProxFarTime, 1000000000); VERBOSE_PRINT("STATS: enabled %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ", prox enabled %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ", prox far %02" PRIu32 ":%02" PRIu32 ":%02" PRIu32 ", prox *->f %" PRIu32 ", prox *->n %" PRIu32, enabledSeconds / 3600, (enabledSeconds % 3600) / 60, enabledSeconds % 60, proxEnabledSeconds / 3600, (proxEnabledSeconds % 3600) / 60, proxEnabledSeconds % 60, proxFarSeconds / 3600, (proxFarSeconds % 3600) / 60, proxFarSeconds % 60, mTask.stats.totalProxBecomesFar, mTask.stats.totalProxBecomesNear); // If the task is disabled, that means the AP is on and has switched the I2C // mux. Therefore, no I2C transactions will succeed so skip them. if (mTask.gestureEnabled) { setGesturePower(false, true /* skipI2c */); } mTask.on = on; configProx(on); return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, mTask.on, 0); } static bool callbackFirmwareUpload(void *cookie) { return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0); } static bool callbackSetRate(uint32_t rate, uint64_t latency, void *cookie) { return sensorSignalInternalEvt(mTask.handle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency); } static bool callbackFlush(void *cookie) { return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_DOUBLE_TOUCH), SENSOR_DATA_EVENT_FLUSH, NULL); } static const struct SensorInfo mSensorInfo = { .sensorName = "Double Touch", .sensorType = SENS_TYPE_DOUBLE_TOUCH, .numAxis = NUM_AXIS_EMBEDDED, .interrupt = NANOHUB_INT_WAKEUP, .minSamples = 20 }; static const struct SensorOps mSensorOps = { .sensorPower = callbackPower, .sensorFirmwareUpload = callbackFirmwareUpload, .sensorSetRate = callbackSetRate, .sensorFlush = callbackFlush, }; static void processI2cResponse(struct I2cTransfer *xfer) { struct I2cTransfer *nextXfer; union EmbeddedDataPoint sample; switch (xfer->state) { case STATE_ENABLE_0: setSleepEnable(false, STATE_ENABLE_1); break; case STATE_ENABLE_1: // HACK: DozeService reactivates pickup gesture before the screen // comes on, so we need to wait for some time after enabling before // trying to talk to touch controller. We may see the touch // controller on the first few samples and then have communication // switched off. So, wait HACK_RETRY_SKIP_COUNT samples before we // consider the transaction. if (mTask.retryCnt < HACK_RETRY_SKIP_COUNT) { setRetryTimer(); } else { setReportingMode(S3708_REPORT_MODE_LPWG, STATE_ENABLE_2); } break; case STATE_ENABLE_2: // Poll the GPIO line to see if it is low/active (it might have been // low when we enabled the ISR, e.g. due to a pending touch event). // Only do this after arming the LPWG, so it happens after we know // that we can talk to the touch controller. if (!gpioGet(mTask.pin)) { osEnqueuePrivateEvt(EVT_SENSOR_TOUCH_INTERRUPT, NULL, NULL, mTask.id); } break; case STATE_DISABLE_0: setSleepEnable(true, STATE_IDLE); break; case STATE_INT_HANDLE_0: // If the interrupt was from the LPWG function, read the function interrupt status register if (xfer->txrxBuf[1] & S3708_INT_STATUS_LPWG) { nextXfer = allocXfer(STATE_INT_HANDLE_1); if (nextXfer != NULL) { nextXfer->txrxBuf[0] = S3708_REG_DATA_BASE + S3708_REG_DATA_4_OFFSET; performXfer(nextXfer, 1, 5); } } break; case STATE_INT_HANDLE_1: // Verify the LPWG interrupt status if (xfer->txrxBuf[0] & S3708_INT_STATUS_DOUBLE_TAP) { DEBUG_PRINT("Sending event"); sample.idata = 1; osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_DOUBLE_TOUCH), sample.vptr, NULL); } break; default: break; } } static void handleI2cEvent(struct I2cTransfer *xfer) { if (xfer->err == 0) { processI2cResponse(xfer); } else if (xfer->state == STATE_ENABLE_0 || xfer->state == STATE_ENABLE_1) { setRetryTimer(); } xfer->inUse = false; } static void handleEvent(uint32_t evtType, const void* evtData) { struct I2cTransfer *xfer; union EmbeddedDataPoint embeddedSample; enum ProxState lastProxState; int ret; switch (evtType) { case EVT_APP_START: osEventUnsubscribe(mTask.id, EVT_APP_START); ret = i2cMasterRequest(I2C_BUS_ID, I2C_SPEED); // Since the i2c bus can be shared with other drivers, it is // possible that one of the other drivers requested the bus first. // Therefore, either 0 or -EBUSY is an acceptable return. if ((ret < 0) && (ret != -EBUSY)) { ERROR_PRINT("i2cMasterRequest() failed!"); } sensorFind(SENS_TYPE_PROX, 0, &mTask.proxHandle); sensorRegisterInitComplete(mTask.handle); break; case EVT_SENSOR_I2C: handleI2cEvent((struct I2cTransfer *)evtData); break; case EVT_SENSOR_TOUCH_INTERRUPT: if (mTask.on) { // Read the interrupt status register xfer = allocXfer(STATE_INT_HANDLE_0); if (xfer != NULL) { xfer->txrxBuf[0] = S3708_REG_F01_DATA_BASE; performXfer(xfer, 1, 2); } } break; case EVT_SENSOR_PROX: if (mTask.on) { // cast off the const, and cast to union embeddedSample = (union EmbeddedDataPoint)((void*)evtData); lastProxState = mTask.proxState; mTask.proxState = (embeddedSample.fdata < PROXIMITY_THRESH_NEAR) ? PROX_STATE_NEAR : PROX_STATE_FAR; if ((lastProxState != PROX_STATE_FAR) && (mTask.proxState == PROX_STATE_FAR)) { ++mTask.stats.totalProxBecomesFar; mTask.stats.lastProxFarTimestamp = sensorGetTime(); setGesturePower(true, false); } else if ((lastProxState != PROX_STATE_NEAR) && (mTask.proxState == PROX_STATE_NEAR)) { ++mTask.stats.totalProxBecomesNear; if (lastProxState == PROX_STATE_FAR) { mTask.stats.totalProxFarTime += sensorGetTime() - mTask.stats.lastProxFarTimestamp; setGesturePower(false, false); } } } break; case EVT_SENSOR_RETRY_TIMER: if (mTask.on) { // Set page number to 0x00 writeRegister(S3708_REG_PAGE_SELECT, 0x00, STATE_ENABLE_0); } break; } } static bool startTask(uint32_t taskId) { mTask.id = taskId; mTask.handle = sensorRegister(&mSensorInfo, &mSensorOps, NULL, false); mTask.pin = gpioRequest(TOUCH_PIN); gpioConfigInput(mTask.pin, GPIO_SPEED_LOW, GPIO_PULL_NONE); syscfgSetExtiPort(mTask.pin); mTask.isr.func = touchIsr; mTask.stats.totalProxBecomesFar = 0; mTask.stats.totalProxBecomesNear = 0; osEventSubscribe(taskId, EVT_APP_START); return true; } static void endTask(void) { enableInterrupt(false); extiUnchainIsr(TOUCH_IRQ, &mTask.isr); extiClearPendingGpio(mTask.pin); gpioRelease(mTask.pin); i2cMasterRelease(I2C_BUS_ID); sensorUnregister(mTask.handle); } INTERNAL_APP_INIT(S3708_APP_ID, S3708_APP_VERSION, startTask, endTask, handleEvent);