C++程序  |  600行  |  18.08 KB

/*
 * 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 <errno.h>
#include <float.h>
#include <stdlib.h>
#include <string.h>

#include <eventnums.h>
#include <gpio.h>
#include <heap.h>
#include <hostIntf.h>
#include <isr.h>
#include <i2c.h>
#include <nanohubPacket.h>
#include <sensors.h>
#include <seos.h>
#include <timer.h>
#include <util.h>

#include <cpu/cpuMath.h>

#include <plat/exti.h>
#include <plat/gpio.h>
#include <plat/syscfg.h>

#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 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, ...) INFO_PRINT(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;

    INFO_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;

    INFO_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);
    INFO_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);