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

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

#define AMS_TMD2772_APP_VERSION 3

#define DRIVER_NAME                            "AMS: "

#define I2C_BUS_ID                             0
#define I2C_SPEED                              400000
#define I2C_ADDR                               0x39

#define AMS_TMD2772_ID                         0x39

#define AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT    0xa0

#define AMS_TMD2772_REG_ENABLE                 (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x00)
#define AMS_TMD2772_REG_ATIME                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x01)
#define AMS_TMD2772_REG_PTIME                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x02)
#define AMS_TMD2772_REG_WTIME                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x03)
#define AMS_TMD2772_REG_AILTL                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x04)
#define AMS_TMD2772_REG_AILTH                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x05)
#define AMS_TMD2772_REG_AIHTL                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x06)
#define AMS_TMD2772_REG_AIHTH                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x07)
#define AMS_TMD2772_REG_PILTL                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x08)
#define AMS_TMD2772_REG_PILTH                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x09)
#define AMS_TMD2772_REG_PIHTL                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x0a)
#define AMS_TMD2772_REG_PIHTH                  (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x0b)
#define AMS_TMD2772_REG_PERS                   (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x0c)
#define AMS_TMD2772_REG_CONFIG                 (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x0d)
#define AMS_TMD2772_REG_PPULSE                 (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x0e)
#define AMS_TMD2772_REG_CONTROL                (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x0f)
#define AMS_TMD2772_REG_ID                     (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x12)
#define AMS_TMD2772_REG_STATUS                 (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x13)
#define AMS_TMD2772_REG_C0DATA                 (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x14)
#define AMS_TMD2772_REG_C0DATAH                (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x15)
#define AMS_TMD2772_REG_C1DATA                 (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x16)
#define AMS_TMD2772_REG_C1DATAH                (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x17)
#define AMS_TMD2772_REG_PDATAL                 (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x18)
#define AMS_TMD2772_REG_PDATAH                 (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x19)
#define AMS_TMD2772_REG_POFFSET                (AMS_TMD2772_CMD_TYPE_AUTO_INCREMENT | 0x1E)

#define AMS_TMD2772_ATIME_SETTING              0xdb
#define AMS_TMD2772_ATIME_MS                   ((256 - AMS_TMD2772_ATIME_SETTING) * 2.73) // in milliseconds
#define AMS_TMD2772_PTIME_SETTING              0xff
#define AMS_TMD2772_PTIME_MS                   ((256 - AMS_TMD2772_PTIME_SETTING) * 2.73) // in milliseconds
#define AMS_TMD2772_WTIME_SETTING_ALS_ON       0xdd // (256 - 221) * 2.73 ms = 95.55 ms
#define AMS_TMD2772_WTIME_SETTING_ALS_OFF      0xb8 // (256 - 184) * 2.73 ms = 196.56 ms
#define AMS_TMD2772_PPULSE_SETTING             8

#define AMS_TMD2772_CAL_DEFAULT_OFFSET         0
#define AMS_TMD2772_CAL_MAX_OFFSET             500

/* AMS_TMD2772_REG_ENABLE */
#define POWER_ON_BIT                           (1 << 0)
#define ALS_ENABLE_BIT                         (1 << 1)
#define PROX_ENABLE_BIT                        (1 << 2)
#define WAIT_ENABLE_BIT                        (1 << 3)

/* AMS_TMD2772_REG_STATUS */
#define PROX_INT_BIT                           (1 << 5)
#define ALS_INT_BIT                            (1 << 4)
#define PROX_VALID_BIT                         (1 << 1)
#define ALS_VALID_BIT                          (1 << 0)

#define AMS_TMD2772_REPORT_NEAR_VALUE          0.0f // centimeters
#define AMS_TMD2772_REPORT_FAR_VALUE           5.0f // centimeters

#define AMS_TMD2772_THRESHOLD_ASSERT_NEAR      300   // in PS units
#define AMS_TMD2772_THRESHOLD_DEASSERT_NEAR    150   // in PS units

#define AMS_TMD2772_ALS_MAX_CHANNEL_COUNT      37888 // in raw data
#define AMS_TMD2772_ALS_MAX_REPORT_VALUE       10000 // in lux

#define AMS_TMD2772_ALS_INVALID                UINT32_MAX

/* Used when SENSOR_RATE_ONCHANGE is requested */
#define AMS_TMD2772_DEFAULT_RATE               SENSOR_HZ(5)

#define AMS_TMD2772_MAX_PENDING_I2C_REQUESTS   4
#define AMS_TMD2772_MAX_I2C_TRANSFER_SIZE      16

/* Private driver events */
enum SensorEvents
{
    EVT_SENSOR_I2C = EVT_APP_START + 1,
    EVT_SENSOR_ALS_TIMER,
    EVT_SENSOR_PROX_TIMER,
};

/* I2C state machine */
enum SensorState
{
    SENSOR_STATE_VERIFY_ID,
    SENSOR_STATE_INIT,

    SENSOR_STATE_CALIBRATE_RESET,
    SENSOR_STATE_CALIBRATE_START,
    SENSOR_STATE_CALIBRATE_ENABLING,
    SENSOR_STATE_CALIBRATE_POLLING_STATUS,
    SENSOR_STATE_CALIBRATE_AWAITING_SAMPLE,
    SENSOR_STATE_CALIBRATE_DISABLING,

    SENSOR_STATE_ENABLING_ALS,
    SENSOR_STATE_ENABLING_PROX,
    SENSOR_STATE_DISABLING_ALS,
    SENSOR_STATE_DISABLING_PROX,

    SENSOR_STATE_IDLE,
    SENSOR_STATE_SAMPLING,
};

enum ProxState
{
    PROX_STATE_INIT,
    PROX_STATE_NEAR,
    PROX_STATE_FAR,
};

struct I2cTransfer
{
    size_t tx;
    size_t rx;
    int err;

    union {
        uint8_t bytes[AMS_TMD2772_MAX_I2C_TRANSFER_SIZE];
        struct {
            uint8_t status;
            uint16_t als[2];
            uint16_t prox;
        } __attribute__((packed)) sample;
        struct {
            uint16_t prox;
        } calibration;
    } txrxBuf;

    uint8_t state;
    bool inUse;
};


struct SensorData
{
    uint32_t tid;

    uint32_t alsHandle;
    uint32_t proxHandle;
    uint32_t alsTimerHandle;
    uint32_t proxTimerHandle;
    uint32_t calibrationSampleTotal;

    struct I2cTransfer transfers[AMS_TMD2772_MAX_PENDING_I2C_REQUESTS];

    union EmbeddedDataPoint lastAlsSample;

    uint8_t calibrationSampleCount;
    uint8_t proxState; // enum ProxState

    bool alsOn;
    bool alsReading;
    bool proxOn;
    bool proxReading;
};

static struct SensorData mData;

/* TODO: check rates are supported */
static const uint32_t supportedRates[] =
{
    SENSOR_HZ(0.1),
    SENSOR_HZ(1),
    SENSOR_HZ(4),
    SENSOR_HZ(5),
    SENSOR_RATE_ONCHANGE,
    0
};

static const uint64_t rateTimerVals[] = //should match "supported rates in length" and be the timer length for that rate in nanosecs
{
    10 * 1000000000ULL,
     1 * 1000000000ULL,
    1000000000ULL / 4,
    1000000000ULL / 5,
};

/*
 * Helper functions
 */

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, mData.tid);
    if (err != 0)
        osLog(LOG_INFO, DRIVER_NAME "i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err);
}

// 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(mData.transfers); i++) {
        if (!mData.transfers[i].inUse) {
            mData.transfers[i].inUse = true;
            mData.transfers[i].state = state;
            return &mData.transfers[i];
        }
    }

    osLog(LOG_ERROR, DRIVER_NAME "Ran out of i2c buffers!");
    return NULL;
}

// 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);
    int ret = -1;

    if (xfer != NULL) {
        xfer->txrxBuf.bytes[0] = reg;
        xfer->txrxBuf.bytes[1] = value;
        ret = i2cMasterTx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf.bytes, 2, i2cCallback, xfer);
    }

    return (ret == 0);
}

static void alsTimerCallback(uint32_t timerId, void *cookie)
{
    osEnqueuePrivateEvt(EVT_SENSOR_ALS_TIMER, cookie, NULL, mData.tid);
}

static void proxTimerCallback(uint32_t timerId, void *cookie)
{
    osEnqueuePrivateEvt(EVT_SENSOR_PROX_TIMER, cookie, NULL, mData.tid);
}

static inline float getLuxFromAlsData(uint16_t als0, uint16_t als1)
{
    float cpl = 1.0f / AMS_TMD2772_ATIME_MS;
    float GA;

    if ((als0 * 10) < (als1 * 21)) {
        // A light
        GA = 0.274f;
    } else if (((als0 * 10) >= (als1 * 21)) && ((als0 * 10) <= (als1 * 43)) && (als0 > 300)) {
        // D65
        GA = 0.592f;
    } else {
        // cool white
        GA = 1.97f;
    }

    float lux1 = GA * 207 * (als0 - (1.799 * als1)) * cpl;
    float lux2 = GA * 207 * ((0.188f * als0) - (0.303 * als1)) * cpl;

    if ((als0 >= AMS_TMD2772_ALS_MAX_CHANNEL_COUNT) ||
        (als1 >= AMS_TMD2772_ALS_MAX_CHANNEL_COUNT)) {
        return AMS_TMD2772_ALS_MAX_REPORT_VALUE;
    } else if ((lux1 > lux2) && (lux1 > 0.0f)) {
        return lux1 > AMS_TMD2772_ALS_MAX_REPORT_VALUE ? AMS_TMD2772_ALS_MAX_REPORT_VALUE : lux1;
    } else if (lux2 > 0.0f) {
        return lux2 > AMS_TMD2772_ALS_MAX_REPORT_VALUE ? AMS_TMD2772_ALS_MAX_REPORT_VALUE : lux2;
    } else {
        return 0.0f;
    }
}

static void setMode(bool alsOn, bool proxOn, uint8_t state)
{
    struct I2cTransfer *xfer;

    xfer = allocXfer(state);
    if (xfer != NULL) {
        xfer->txrxBuf.bytes[0] = AMS_TMD2772_REG_ENABLE;
        xfer->txrxBuf.bytes[1] = POWER_ON_BIT | WAIT_ENABLE_BIT |
                                 (alsOn ? ALS_ENABLE_BIT : 0) | (proxOn ? PROX_ENABLE_BIT : 0);
        xfer->txrxBuf.bytes[2] = AMS_TMD2772_ATIME_SETTING;
        xfer->txrxBuf.bytes[3] = AMS_TMD2772_PTIME_SETTING;
        xfer->txrxBuf.bytes[4] = alsOn ? AMS_TMD2772_WTIME_SETTING_ALS_ON : AMS_TMD2772_WTIME_SETTING_ALS_OFF;
        i2cMasterTx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf.bytes, 5, i2cCallback, xfer);
    }
}

static bool sensorPowerAls(bool on, void *cookie)
{
    osLog(LOG_INFO, DRIVER_NAME "sensorPowerAls: %d\n", on);

    if (mData.alsTimerHandle) {
        timTimerCancel(mData.alsTimerHandle);
        mData.alsTimerHandle = 0;
        mData.alsReading = false;
    }

    mData.lastAlsSample.idata = AMS_TMD2772_ALS_INVALID;
    mData.alsOn = on;
    setMode(on, mData.proxOn, (on ? SENSOR_STATE_ENABLING_ALS : SENSOR_STATE_DISABLING_ALS));

    return true;
}

static bool sensorFirmwareAls(void *cookie)
{
    sensorSignalInternalEvt(mData.alsHandle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0);
    return true;
}

static bool sensorRateAls(uint32_t rate, uint64_t latency, void *cookie)
{
    if (rate == SENSOR_RATE_ONCHANGE) {
        rate = AMS_TMD2772_DEFAULT_RATE;
    }
    osLog(LOG_INFO, DRIVER_NAME "sensorRateAls: %ld/%lld\n", rate, latency);

    if (mData.alsTimerHandle)
        timTimerCancel(mData.alsTimerHandle);
    mData.alsTimerHandle = timTimerSet(sensorTimerLookupCommon(supportedRates, rateTimerVals, rate), 0, 50, alsTimerCallback, NULL, false);
    osEnqueuePrivateEvt(EVT_SENSOR_ALS_TIMER, NULL, NULL, mData.tid);
    sensorSignalInternalEvt(mData.alsHandle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);

    return true;
}

static bool sensorFlushAls(void *cookie)
{
    return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_ALS), SENSOR_DATA_EVENT_FLUSH, NULL);
}

static bool sendLastSampleAls(void *cookie, uint32_t tid) {
    bool result = true;

    // If we don't end up doing anything here, the expectation is that we are powering up/haven't got the
    // first sample yet, so a broadcast event will go out soon with the first sample
    if (mData.lastAlsSample.idata != AMS_TMD2772_ALS_INVALID) {
        result = osEnqueuePrivateEvt(sensorGetMyEventType(SENS_TYPE_ALS), mData.lastAlsSample.vptr, NULL, tid);
    }
    return result;
}

static bool sensorPowerProx(bool on, void *cookie)
{
    osLog(LOG_INFO, DRIVER_NAME "sensorPowerProx: %d\n", on);

    if (mData.proxTimerHandle) {
        timTimerCancel(mData.proxTimerHandle);
        mData.proxTimerHandle = 0;
        mData.proxReading = false;
    }

    mData.proxState = PROX_STATE_INIT;
    mData.proxOn = on;
    setMode(mData.alsOn, on, (on ? SENSOR_STATE_ENABLING_PROX : SENSOR_STATE_DISABLING_PROX));

    return true;
}

static bool sensorFirmwareProx(void *cookie)
{
    sensorSignalInternalEvt(mData.proxHandle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0);
    return true;
}

static bool sensorRateProx(uint32_t rate, uint64_t latency, void *cookie)
{
    if (rate == SENSOR_RATE_ONCHANGE) {
        rate = AMS_TMD2772_DEFAULT_RATE;
    }
    osLog(LOG_INFO, DRIVER_NAME "sensorRateProx: %ld/%lld\n", rate, latency);

    if (mData.proxTimerHandle)
        timTimerCancel(mData.proxTimerHandle);
    mData.proxTimerHandle = timTimerSet(sensorTimerLookupCommon(supportedRates, rateTimerVals, rate), 0, 50, proxTimerCallback, NULL, false);
    osEnqueuePrivateEvt(EVT_SENSOR_PROX_TIMER, NULL, NULL, mData.tid);
    sensorSignalInternalEvt(mData.proxHandle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency);

    return true;
}

static bool sensorFlushProx(void *cookie)
{
    return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_PROX), SENSOR_DATA_EVENT_FLUSH, NULL);
}

static bool sendLastSampleProx(void *cookie, uint32_t tid) {
    union EmbeddedDataPoint sample;
    bool result = true;

    // See note in sendLastSampleAls
    if (mData.proxState != PROX_STATE_INIT) {
        sample.fdata = (mData.proxState == PROX_STATE_NEAR) ?
            AMS_TMD2772_REPORT_NEAR_VALUE : AMS_TMD2772_REPORT_FAR_VALUE;
        result = osEnqueuePrivateEvt(sensorGetMyEventType(SENS_TYPE_PROX), sample.vptr, NULL, tid);
    }
    return result;
}

static const struct SensorInfo sensorInfoAls =
{
    .sensorName = "ALS",
    .supportedRates = supportedRates,
    .sensorType = SENS_TYPE_ALS,
    .numAxis = NUM_AXIS_EMBEDDED,
    .interrupt = NANOHUB_INT_NONWAKEUP,
    .minSamples = 20
};

static const struct SensorOps sensorOpsAls =
{
    .sensorPower = sensorPowerAls,
    .sensorFirmwareUpload = sensorFirmwareAls,
    .sensorSetRate = sensorRateAls,
    .sensorFlush = sensorFlushAls,
    .sensorTriggerOndemand = NULL,
    .sensorCalibrate = NULL,
    .sensorSendOneDirectEvt = sendLastSampleAls
};

static const struct SensorInfo sensorInfoProx =
{
    .sensorName = "Proximity",
    .supportedRates = supportedRates,
    .sensorType = SENS_TYPE_PROX,
    .numAxis = NUM_AXIS_EMBEDDED,
    .interrupt = NANOHUB_INT_WAKEUP,
    .minSamples = 300
};

static const struct SensorOps sensorOpsProx =
{
    .sensorPower = sensorPowerProx,
    .sensorFirmwareUpload = sensorFirmwareProx,
    .sensorSetRate = sensorRateProx,
    .sensorFlush = sensorFlushProx,
    .sensorTriggerOndemand = NULL,
    .sensorCalibrate = NULL,
    .sensorSendOneDirectEvt = sendLastSampleProx
};

/*
 * Sensor i2c state machine
 */

static void handle_calibration_event(struct I2cTransfer *xfer) {
    struct I2cTransfer *nextXfer;

    switch (xfer->state) {
    case SENSOR_STATE_CALIBRATE_RESET:
        mData.calibrationSampleCount = 0;
        mData.calibrationSampleTotal = 0;
        /* Intentional fall-through */

    case SENSOR_STATE_CALIBRATE_START:
        writeRegister(AMS_TMD2772_REG_ENABLE, POWER_ON_BIT | PROX_ENABLE_BIT, SENSOR_STATE_CALIBRATE_ENABLING);
        break;

    case SENSOR_STATE_CALIBRATE_ENABLING:
        nextXfer = allocXfer(SENSOR_STATE_CALIBRATE_POLLING_STATUS);
        if (nextXfer != NULL) {
            nextXfer->txrxBuf.bytes[0] = AMS_TMD2772_REG_STATUS;
            i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, nextXfer->txrxBuf.bytes, 1, nextXfer->txrxBuf.bytes, 1, i2cCallback, nextXfer);
        }
        break;

    case SENSOR_STATE_CALIBRATE_POLLING_STATUS:
        if (xfer->txrxBuf.bytes[0] & PROX_INT_BIT) {
            /* Done */
            nextXfer = allocXfer(SENSOR_STATE_CALIBRATE_AWAITING_SAMPLE);
            if (nextXfer != NULL) {
                nextXfer->txrxBuf.bytes[0] = AMS_TMD2772_REG_PDATAL;
                i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, nextXfer->txrxBuf.bytes, 1, nextXfer->txrxBuf.bytes, 2, i2cCallback, nextXfer);
            }
        } else {
            /* Poll again; go back to previous state */
            xfer->state = SENSOR_STATE_CALIBRATE_ENABLING;
            handle_calibration_event(xfer);
        }
        break;

    case SENSOR_STATE_CALIBRATE_AWAITING_SAMPLE:
        mData.calibrationSampleCount++;
        mData.calibrationSampleTotal += xfer->txrxBuf.calibration.prox;

        writeRegister(AMS_TMD2772_REG_ENABLE, 0x00, SENSOR_STATE_CALIBRATE_DISABLING);
        break;

    case SENSOR_STATE_CALIBRATE_DISABLING:
        if (mData.calibrationSampleCount >= 20) {
            /* Done, calculate calibration */
            uint16_t average = mData.calibrationSampleTotal / mData.calibrationSampleCount;
            uint16_t crosstalk = (average > 0x7f) ? 0x7f : average;

            writeRegister(AMS_TMD2772_REG_POFFSET, crosstalk, SENSOR_STATE_IDLE);
        } else {
            /* Get another sample; go back to earlier state */
            xfer->state = SENSOR_STATE_CALIBRATE_START;
            handle_calibration_event(xfer);
        }
        break;

    default:
        break;
    }
}

static void handle_i2c_event(struct I2cTransfer *xfer)
{
    union EmbeddedDataPoint sample;
    bool sendData;
    struct I2cTransfer *nextXfer;

    switch (xfer->state) {
    case SENSOR_STATE_VERIFY_ID:
        /* Check the sensor ID */
        if (xfer->err != 0 || xfer->txrxBuf.bytes[0] != AMS_TMD2772_ID) {
            osLog(LOG_INFO, DRIVER_NAME "not detected\n");
            sensorUnregister(mData.alsHandle);
            sensorUnregister(mData.proxHandle);
            break;
        }

        nextXfer = allocXfer(SENSOR_STATE_INIT);
        if (nextXfer != NULL) {
            /* Start address */
            nextXfer->txrxBuf.bytes[0] = AMS_TMD2772_REG_ENABLE;
            /* ENABLE */
            nextXfer->txrxBuf.bytes[1] = 0x00;
            /* ATIME */
            nextXfer->txrxBuf.bytes[2] = AMS_TMD2772_ATIME_SETTING;
            /* PTIME */
            nextXfer->txrxBuf.bytes[3] = AMS_TMD2772_PTIME_SETTING;
            /* WTIME */
            nextXfer->txrxBuf.bytes[4] = 0xFF;
            i2cMasterTx(I2C_BUS_ID, I2C_ADDR, nextXfer->txrxBuf.bytes, 5, i2cCallback, nextXfer);
        }
        break;

    case SENSOR_STATE_INIT:
        nextXfer = allocXfer(SENSOR_STATE_IDLE);
        if (nextXfer != NULL) {
            /* Start address */
            nextXfer->txrxBuf.bytes[0] = AMS_TMD2772_REG_PERS;
            /* PERS */
            nextXfer->txrxBuf.bytes[1] = 0x00;
            /* CONFIG */
            nextXfer->txrxBuf.bytes[2] = 0x00;
            /* PPULSE */
            nextXfer->txrxBuf.bytes[3] = AMS_TMD2772_PPULSE_SETTING;
            /* CONTROL */
            nextXfer->txrxBuf.bytes[4] = 0x20;
            i2cMasterTx(I2C_BUS_ID, I2C_ADDR, nextXfer->txrxBuf.bytes, 5, i2cCallback, nextXfer);
        }
        break;

    case SENSOR_STATE_IDLE:
        sensorRegisterInitComplete(mData.alsHandle);
        sensorRegisterInitComplete(mData.proxHandle);
        break;

    case SENSOR_STATE_ENABLING_ALS:
        sensorSignalInternalEvt(mData.alsHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, true, 0);
        break;

    case SENSOR_STATE_ENABLING_PROX:
        sensorSignalInternalEvt(mData.proxHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, true, 0);
        break;

    case SENSOR_STATE_DISABLING_ALS:
        sensorSignalInternalEvt(mData.alsHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, false, 0);
        break;

    case SENSOR_STATE_DISABLING_PROX:
        sensorSignalInternalEvt(mData.proxHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, false, 0);
        break;

    case SENSOR_STATE_SAMPLING:
#if 0
        osLog(LOG_INFO, DRIVER_NAME "sample ready: status=%02x prox=%u als0=%u als1=%u\n",
              xfer->txrxBuf.sample.status, xfer->txrxBuf.sample.prox,
              xfer->txrxBuf.sample.als[0], xfer->txrxBuf.sample.als[1]);
#endif

        if (mData.alsOn && mData.alsReading &&
            (xfer->txrxBuf.sample.status & ALS_VALID_BIT)) {
            /* Create event */
            sample.fdata = getLuxFromAlsData(xfer->txrxBuf.sample.als[0],
                                             xfer->txrxBuf.sample.als[1]);
            if (mData.lastAlsSample.idata != sample.idata) {
                osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_ALS), sample.vptr, NULL);
                mData.lastAlsSample.fdata = sample.fdata;
            }
        }

        if (mData.proxOn && mData.proxReading &&
            (xfer->txrxBuf.sample.status & PROX_VALID_BIT)) {
            /* Create event */
            sendData = true;
            if (mData.proxState == PROX_STATE_INIT) {
                if (xfer->txrxBuf.sample.prox > AMS_TMD2772_THRESHOLD_ASSERT_NEAR) {
                    sample.fdata = AMS_TMD2772_REPORT_NEAR_VALUE;
                    mData.proxState = PROX_STATE_NEAR;
                } else {
                    sample.fdata = AMS_TMD2772_REPORT_FAR_VALUE;
                    mData.proxState = PROX_STATE_FAR;
                }
            } else {
                if (mData.proxState == PROX_STATE_NEAR &&
                    xfer->txrxBuf.sample.prox < AMS_TMD2772_THRESHOLD_DEASSERT_NEAR) {
                    sample.fdata = AMS_TMD2772_REPORT_FAR_VALUE;
                    mData.proxState = PROX_STATE_FAR;
                } else if (mData.proxState == PROX_STATE_FAR &&
                    xfer->txrxBuf.sample.prox > AMS_TMD2772_THRESHOLD_ASSERT_NEAR) {
                    sample.fdata = AMS_TMD2772_REPORT_NEAR_VALUE;
                    mData.proxState = PROX_STATE_NEAR;
                } else {
                    sendData = false;
                }
            }

            if (sendData)
                osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_PROX), sample.vptr, NULL);
        }

        mData.alsReading = false;
        mData.proxReading = false;
        break;

    default:
        handle_calibration_event(xfer);
        break;
    }

    xfer->inUse = false;
}

/*
 * Main driver entry points
 */

static bool init_app(uint32_t myTid)
{
    /* Set up driver private data */
    mData.tid = myTid;
    mData.alsOn = false;
    mData.alsReading = false;
    mData.proxOn = false;
    mData.proxReading = false;
    mData.lastAlsSample.idata = AMS_TMD2772_ALS_INVALID;
    mData.proxState = PROX_STATE_INIT;

    /* Register sensors */
    mData.alsHandle = sensorRegister(&sensorInfoAls, &sensorOpsAls, NULL, false);
    mData.proxHandle = sensorRegister(&sensorInfoProx, &sensorOpsProx, NULL, false);

    osEventSubscribe(myTid, EVT_APP_START);

    return true;
}

static void end_app(void)
{
    sensorUnregister(mData.alsHandle);
    sensorUnregister(mData.proxHandle);

    i2cMasterRelease(I2C_BUS_ID);
}

static void handle_event(uint32_t evtType, const void* evtData)
{
    struct I2cTransfer *xfer;

    switch (evtType) {
    case EVT_APP_START:
        osEventUnsubscribe(mData.tid, EVT_APP_START);
        i2cMasterRequest(I2C_BUS_ID, I2C_SPEED);

        /* TODO: reset chip first */
        xfer = allocXfer(SENSOR_STATE_VERIFY_ID);
        if (xfer != NULL) {
            xfer->txrxBuf.bytes[0] = AMS_TMD2772_REG_ID;
            i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf.bytes, 1, xfer->txrxBuf.bytes, 1, i2cCallback, xfer);
        }
        break;

    case EVT_SENSOR_I2C:
        handle_i2c_event((struct I2cTransfer *)evtData);
        break;

    case EVT_SENSOR_ALS_TIMER:
    case EVT_SENSOR_PROX_TIMER:
        /* Start sampling for a value */
        if (!mData.alsReading && !mData.proxReading) {
            xfer = allocXfer(SENSOR_STATE_SAMPLING);
            if (xfer != NULL) {
                xfer->txrxBuf.bytes[0] = AMS_TMD2772_REG_STATUS;
                i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf.bytes, 1, xfer->txrxBuf.bytes, 7, i2cCallback, xfer);
            }
        }

        if (evtType == EVT_SENSOR_ALS_TIMER)
            mData.alsReading = true;
        else
            mData.proxReading = true;
        break;
    }
}

INTERNAL_APP_INIT(APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 9), AMS_TMD2772_APP_VERSION, init_app, end_app, handle_event);