/* * 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 <eventnums.h> #include <gpio.h> #include <timer.h> #include <sensors.h> #include <heap.h> #include <hostIntf.h> #include <isr.h> #include <i2c.h> #include <nanohubPacket.h> #include <sensors.h> #include <seos.h> #include <plat/exti.h> #include <plat/gpio.h> #include <plat/syscfg.h> #include <variant/variant.h> #define AMS_TMD4903_APP_ID APP_ID_MAKE(NANOHUB_VENDOR_GOOGLE, 12) #define AMS_TMD4903_APP_VERSION 14 #ifndef PROX_INT_PIN #error "PROX_INT_PIN is not defined; please define in variant.h" #endif #ifndef PROX_IRQ #error "PROX_IRQ is not defined; please define in variant.h" #endif #define I2C_BUS_ID 0 #define I2C_SPEED 400000 #define I2C_ADDR 0x39 #define AMS_TMD4903_REG_ENABLE 0x80 #define AMS_TMD4903_REG_ATIME 0x81 #define AMS_TMD4903_REG_PTIME 0x82 #define AMS_TMD4903_REG_WTIME 0x83 #define AMS_TMD4903_REG_AILTL 0x84 #define AMS_TMD4903_REG_AILTH 0x85 #define AMS_TMD4903_REG_AIHTL 0x86 #define AMS_TMD4903_REG_AIHTH 0x87 #define AMS_TMD4903_REG_PILTL 0x88 #define AMS_TMD4903_REG_PILTH 0x89 #define AMS_TMD4903_REG_PIHTL 0x8a #define AMS_TMD4903_REG_PIHTH 0x8b #define AMS_TMD4903_REG_PERS 0x8c #define AMS_TMD4903_REG_CFG0 0x8d #define AMS_TMD4903_REG_PGCFG0 0x8e #define AMS_TMD4903_REG_PGCFG1 0x8f #define AMS_TMD4903_REG_CFG1 0x90 #define AMS_TMD4903_REG_REVID 0x91 #define AMS_TMD4903_REG_ID 0x92 #define AMS_TMD4903_REG_STATUS 0x93 #define AMS_TMD4903_REG_CDATAL 0x94 #define AMS_TMD4903_REG_CDATAH 0x95 #define AMS_TMD4903_REG_RDATAL 0x96 #define AMS_TMD4903_REG_RDATAH 0x97 #define AMS_TMD4903_REG_GDATAL 0x98 #define AMS_TMD4903_REG_GDATAH 0x99 #define AMS_TMD4903_REG_BDATAL 0x9A #define AMS_TMD4903_REG_BDATAH 0x9B #define AMS_TMD4903_REG_PDATAL 0x9C #define AMS_TMD4903_REG_PDATAH 0x9D #define AMS_TMD4903_REG_STATUS2 0x9E #define AMS_TMD4903_REG_CFG4 0xAC #define AMS_TMD4903_REG_OFFSETNL 0xC0 #define AMS_TMD4903_REG_OFFSETNH 0xC1 #define AMS_TMD4903_REG_OFFSETSL 0xC2 #define AMS_TMD4903_REG_OFFSETSH 0xC3 #define AMS_TMD4903_REG_OFFSETWL 0xC4 #define AMS_TMD4903_REG_OFFSETWH 0xC5 #define AMS_TMD4903_REG_OFFSETEL 0xC6 #define AMS_TMD4903_REG_OFFSETEH 0xC7 #define AMS_TMD4903_REG_CALIB 0xD7 #define AMS_TMD4903_REG_INTENAB 0xDD #define AMS_TMD4903_REG_INTCLEAR 0xDE #define AMS_TMD4903_ID 0xB8 #define AMS_TMD4903_DEFAULT_RATE SENSOR_HZ(5) #define AMS_TMD4903_ATIME_SETTING 0xdc #define AMS_TMD4903_ATIME_MS ((256 - AMS_TMD4903_ATIME_SETTING) * 2.78) // in milliseconds #define AMS_TMD4903_MAX_ALS_CHANNEL_COUNT ((256 - AMS_TMD4903_ATIME_SETTING) * 1024) // in ALS data units #define AMS_TMD4903_ALS_MAX_REPORT_VALUE 150000.0f // in lux #define AMS_TMD4903_PTIME_SETTING 0x11 #define AMS_TMD4903_PGCFG0_SETTING 0x41 // pulse length: 8 us, pulse count: 2 #define AMS_TMD4903_PGCFG1_SETTING 0x04 // gain: 1x, drive: 50 mA #define AMS_TMD4903_ALS_DEBOUNCE_SAMPLES 5 #define AMS_TMD4903_ALS_GAIN_4X_THOLD 4000.0f #define AMS_TMD4903_ALS_GAIN_16X_THOLD 1000.0f #define AMS_TMD4903_ALS_GAIN_64X_THOLD 250.0f /* AMS_TMD4903_REG_ENABLE */ #define PROX_INT_ENABLE_BIT (1 << 5) #define ALS_INT_ENABLE_BIT (1 << 4) #define PROX_ENABLE_BIT (1 << 2) #define ALS_ENABLE_BIT (1 << 1) #define POWER_ON_BIT (1 << 0) /* AMS_TMD4903_REG_INTENAB */ #define CAL_INT_ENABLE_BIT (1 << 1) #define AMS_TMD4903_REPORT_NEAR_VALUE 0.0f // centimeters #define AMS_TMD4903_REPORT_FAR_VALUE 5.0f // centimeters #define AMS_TMD4903_PROX_THRESHOLD_HIGH 350 // value in PS_DATA #define AMS_TMD4903_PROX_THRESHOLD_LOW 150 // value in PS_DATA #define AMS_TMD4903_ALS_INVALID UINT32_MAX #define AMS_TMD4903_ALS_TIMER_DELAY 200000000ULL #define AMS_TMD4903_MAX_PENDING_I2C_REQUESTS 8 #define AMS_TMD4903_MAX_I2C_TRANSFER_SIZE 18 #define MIN2(a,b) (((a) < (b)) ? (a) : (b)) #define MAX2(a,b) (((a) > (b)) ? (a) : (b)) // NOTE: Define this to be 1 to enable streaming of proximity samples instead of // using the interrupt #define PROX_STREAMING 0 #define INFO_PRINT(fmt, ...) do { \ osLog(LOG_INFO, "%s " fmt, "[TMD4903]", ##__VA_ARGS__); \ } while (0); #define DEBUG_PRINT(fmt, ...) do { \ if (enable_debug) { \ INFO_PRINT(fmt, ##__VA_ARGS__); \ } \ } while (0); static const bool enable_debug = 0; /* Private driver events */ enum SensorEvents { EVT_SENSOR_I2C = EVT_APP_START + 1, EVT_SENSOR_ALS_TIMER, EVT_SENSOR_ALS_INTERRUPT, EVT_SENSOR_PROX_INTERRUPT, }; /* I2C state machine */ enum SensorState { SENSOR_STATE_VERIFY_ID, SENSOR_STATE_INIT_0, SENSOR_STATE_INIT_1, SENSOR_STATE_INIT_2, SENSOR_STATE_FINISH_INIT, SENSOR_STATE_START_PROX_CALIBRATION_0, SENSOR_STATE_START_PROX_CALIBRATION_1, SENSOR_STATE_FINISH_PROX_CALIBRATION_0, SENSOR_STATE_FINISH_PROX_CALIBRATION_1, SENSOR_STATE_POLL_STATUS, SENSOR_STATE_ENABLING_ALS, SENSOR_STATE_ENABLING_PROX, SENSOR_STATE_DISABLING_ALS, SENSOR_STATE_DISABLING_ALS_2, SENSOR_STATE_DISABLING_PROX, SENSOR_STATE_DISABLING_PROX_2, SENSOR_STATE_DISABLING_PROX_3, SENSOR_STATE_ALS_CHANGING_GAIN, SENSOR_STATE_ALS_SAMPLING, SENSOR_STATE_PROX_SAMPLING, SENSOR_STATE_PROX_TRANSITION_0, SENSOR_STATE_IDLE, }; enum ProxState { PROX_STATE_INIT, PROX_STATE_NEAR, PROX_STATE_FAR, }; enum ProxOffsetIndex { PROX_OFFSET_NORTH = 0, PROX_OFFSET_SOUTH = 1, PROX_OFFSET_WEST = 2, PROX_OFFSET_EAST = 3 }; enum AlsGain { ALS_GAIN_1X = 0, ALS_GAIN_4X = 1, ALS_GAIN_16X = 2, ALS_GAIN_64X = 3 }; struct AlsProxTransfer { size_t tx; size_t rx; int err; uint8_t txrxBuf[AMS_TMD4903_MAX_I2C_TRANSFER_SIZE]; uint8_t state; bool inUse; }; struct SensorData { struct Gpio *pin; struct ChainedIsr isr; uint32_t tid; uint32_t alsHandle; uint32_t proxHandle; uint32_t alsTimerHandle; float alsOffset; union EmbeddedDataPoint lastAlsSample; struct AlsProxTransfer transfers[AMS_TMD4903_MAX_PENDING_I2C_REQUESTS]; uint8_t lastProxState; // enum ProxState uint8_t alsGain; uint8_t nextAlsGain; uint8_t alsDebounceSamples; bool alsOn; bool proxOn; bool alsCalibrating; bool proxCalibrating; bool proxDirectMode; bool alsChangingGain; bool alsSkipSample; }; static struct SensorData mTask; struct AlsCalibrationData { struct HostHubRawPacket header; struct SensorAppEventHeader data_header; float offset; } __attribute__((packed)); struct ProxCalibrationData { struct HostHubRawPacket header; struct SensorAppEventHeader data_header; int32_t offsets[4]; } __attribute__((packed)); static const uint32_t supportedRates[] = { SENSOR_HZ(5), SENSOR_RATE_ONCHANGE, 0, }; static void i2cCallback(void *cookie, size_t tx, size_t rx, int err); /* * Helper functions */ // 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 AlsProxTransfer *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; return &mTask.transfers[i]; } } INFO_PRINT("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 AlsProxTransfer *xfer = allocXfer(state); int ret = -1; if (xfer != NULL) { xfer->txrxBuf[0] = reg; xfer->txrxBuf[1] = value; ret = i2cMasterTx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, 2, i2cCallback, xfer); } return (ret == 0); } static bool proxIsr(struct ChainedIsr *localIsr) { struct SensorData *data = container_of(localIsr, struct SensorData, isr); uint8_t lastProxState = data->lastProxState; union EmbeddedDataPoint sample; bool pinState; if (!extiIsPendingGpio(data->pin)) { return false; } pinState = gpioGet(data->pin); if (data->proxOn) { #if PROX_STREAMING (void)sample; (void)pinState; (void)lastProxState; if (!pinState) osEnqueuePrivateEvt(EVT_SENSOR_PROX_INTERRUPT, NULL, NULL, mTask.tid); #else if (data->proxDirectMode) { sample.fdata = (pinState) ? AMS_TMD4903_REPORT_FAR_VALUE : AMS_TMD4903_REPORT_NEAR_VALUE; data->lastProxState = (pinState) ? PROX_STATE_FAR : PROX_STATE_NEAR; if (data->lastProxState != lastProxState) osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_PROX), sample.vptr, NULL); } else { osEnqueuePrivateEvt(EVT_SENSOR_PROX_INTERRUPT, NULL, NULL, mTask.tid); } #endif } else if (data->alsOn && data->alsCalibrating && !pinState) { osEnqueuePrivateEvt(EVT_SENSOR_ALS_INTERRUPT, NULL, NULL, mTask.tid); } extiClearPendingGpio(data->pin); return true; } static bool enableInterrupt(struct Gpio *pin, struct ChainedIsr *isr, enum ExtiTrigger trigger) { extiEnableIntGpio(pin, trigger); extiChainIsr(PROX_IRQ, isr); return true; } static bool disableInterrupt(struct Gpio *pin, struct ChainedIsr *isr) { extiUnchainIsr(PROX_IRQ, isr); extiDisableIntGpio(pin); return true; } static void i2cCallback(void *cookie, size_t tx, size_t rx, int err) { struct AlsProxTransfer *xfer = cookie; xfer->tx = tx; xfer->rx = rx; xfer->err = err; osEnqueuePrivateEvt(EVT_SENSOR_I2C, cookie, NULL, mTask.tid); if (err != 0) INFO_PRINT("i2c error (tx: %d, rx: %d, err: %d)\n", tx, rx, err); } static void alsTimerCallback(uint32_t timerId, void *cookie) { osEnqueuePrivateEvt(EVT_SENSOR_ALS_TIMER, cookie, NULL, mTask.tid); } static inline uint32_t getAlsGainFromSetting(uint8_t gainSetting) { return 0x1 << (2 * gainSetting); } static float getLuxFromAlsData(uint16_t c, uint16_t r, uint16_t g, uint16_t b) { float lux; // check for channel saturation if (c >= AMS_TMD4903_MAX_ALS_CHANNEL_COUNT || r >= AMS_TMD4903_MAX_ALS_CHANNEL_COUNT || g >= AMS_TMD4903_MAX_ALS_CHANNEL_COUNT || b >= AMS_TMD4903_MAX_ALS_CHANNEL_COUNT) return AMS_TMD4903_ALS_MAX_REPORT_VALUE; lux = (ALS_GA_FACTOR * ((c * ALS_C_COEFF) + (r * ALS_R_COEFF) + (g * ALS_G_COEFF) + (b * ALS_B_COEFF)) / (AMS_TMD4903_ATIME_MS * getAlsGainFromSetting(mTask.alsGain))) * mTask.alsOffset; return MIN2(MAX2(0.0f, lux), AMS_TMD4903_ALS_MAX_REPORT_VALUE); } static bool checkForAlsAutoGain(float sample) { if ((mTask.alsGain != ALS_GAIN_64X) && (sample < AMS_TMD4903_ALS_GAIN_64X_THOLD)) { mTask.alsDebounceSamples = (mTask.nextAlsGain == ALS_GAIN_64X) ? (mTask.alsDebounceSamples + 1) : 1; mTask.nextAlsGain = ALS_GAIN_64X; } else if ((mTask.alsGain != ALS_GAIN_16X) && (sample >= AMS_TMD4903_ALS_GAIN_64X_THOLD) && (sample < AMS_TMD4903_ALS_GAIN_16X_THOLD)) { mTask.alsDebounceSamples = (mTask.nextAlsGain == ALS_GAIN_16X) ? (mTask.alsDebounceSamples + 1) : 1; mTask.nextAlsGain = ALS_GAIN_16X; } else if ((mTask.alsGain != ALS_GAIN_4X) && (sample >= AMS_TMD4903_ALS_GAIN_16X_THOLD) && (sample < AMS_TMD4903_ALS_GAIN_4X_THOLD)) { mTask.alsDebounceSamples = (mTask.nextAlsGain == ALS_GAIN_4X) ? (mTask.alsDebounceSamples + 1) : 1; mTask.nextAlsGain = ALS_GAIN_4X; } else if ((mTask.alsGain != ALS_GAIN_1X) && (sample >= AMS_TMD4903_ALS_GAIN_4X_THOLD)) { mTask.alsDebounceSamples = (mTask.nextAlsGain == ALS_GAIN_1X) ? (mTask.alsDebounceSamples + 1) : 1; mTask.nextAlsGain = ALS_GAIN_1X; } return (mTask.alsDebounceSamples >= AMS_TMD4903_ALS_DEBOUNCE_SAMPLES); } static void sendCalibrationResultAls(uint8_t status, float offset) { struct AlsCalibrationData *data = heapAlloc(sizeof(struct AlsCalibrationData)); if (!data) { osLog(LOG_WARN, "Couldn't alloc als cal result pkt"); return; } data->header.appId = AMS_TMD4903_APP_ID; data->header.dataLen = (sizeof(struct AlsCalibrationData) - sizeof(struct HostHubRawPacket)); data->data_header.msgId = SENSOR_APP_MSG_ID_CAL_RESULT; data->data_header.sensorType = SENS_TYPE_ALS; data->data_header.status = status; data->offset = offset; if (!osEnqueueEvtOrFree(EVT_APP_TO_HOST, data, heapFree)) osLog(LOG_WARN, "Couldn't send als cal result evt"); } static void sendCalibrationResultProx(uint8_t status, int16_t *offsets) { int i; struct ProxCalibrationData *data = heapAlloc(sizeof(struct ProxCalibrationData)); if (!data) { osLog(LOG_WARN, "Couldn't alloc prox cal result pkt"); return; } data->header.appId = AMS_TMD4903_APP_ID; data->header.dataLen = (sizeof(struct ProxCalibrationData) - sizeof(struct HostHubRawPacket)); data->data_header.msgId = SENSOR_APP_MSG_ID_CAL_RESULT; data->data_header.sensorType = SENS_TYPE_PROX; data->data_header.status = status; // The offsets are cast from int16_t to int32_t, so I can't use memcpy for (i = 0; i < 4; i++) data->offsets[i] = offsets[i]; if (!osEnqueueEvtOrFree(EVT_APP_TO_HOST, data, heapFree)) osLog(LOG_WARN, "Couldn't send prox cal result evt"); } static void setMode(bool alsOn, bool proxOn, uint8_t state) { uint8_t regEnable = ((alsOn || proxOn) ? POWER_ON_BIT : 0) | (alsOn ? ALS_ENABLE_BIT : 0) | (proxOn ? (PROX_INT_ENABLE_BIT | PROX_ENABLE_BIT) : 0); writeRegister(AMS_TMD4903_REG_ENABLE, regEnable, state); } static bool sensorPowerAls(bool on, void *cookie) { DEBUG_PRINT("sensorPowerAls: %d\n", on); if (on && !mTask.alsTimerHandle) { mTask.alsTimerHandle = timTimerSet(AMS_TMD4903_ALS_TIMER_DELAY, 0, 50, alsTimerCallback, NULL, false); } else if (!on && mTask.alsTimerHandle) { timTimerCancel(mTask.alsTimerHandle); mTask.alsTimerHandle = 0; } mTask.lastAlsSample.idata = AMS_TMD4903_ALS_INVALID; mTask.alsOn = on; mTask.nextAlsGain = ALS_GAIN_4X; mTask.alsChangingGain = false; // skip first sample to make sure we get an entire integration cycle mTask.alsSkipSample = true; mTask.alsDebounceSamples = 0; setMode(on, mTask.proxOn, (on) ? SENSOR_STATE_ENABLING_ALS : SENSOR_STATE_DISABLING_ALS); return true; } static bool sensorFirmwareAls(void *cookie) { return sensorSignalInternalEvt(mTask.alsHandle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0); } static bool sensorRateAls(uint32_t rate, uint64_t latency, void *cookie) { if (rate == SENSOR_RATE_ONCHANGE) rate = AMS_TMD4903_DEFAULT_RATE; DEBUG_PRINT("sensorRateAls: rate=%ld Hz latency=%lld ns\n", rate/1024, latency); return sensorSignalInternalEvt(mTask.alsHandle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency); } static bool sensorFlushAls(void *cookie) { return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_ALS), SENSOR_DATA_EVENT_FLUSH, NULL); } static bool sensorCalibrateAls(void *cookie) { DEBUG_PRINT("sensorCalibrateAls"); if (mTask.alsOn || mTask.proxOn) { INFO_PRINT("cannot calibrate while als or prox are active\n"); sendCalibrationResultAls(SENSOR_APP_EVT_STATUS_BUSY, 0.0f); return false; } mTask.alsOn = true; mTask.lastAlsSample.idata = AMS_TMD4903_ALS_INVALID; mTask.alsCalibrating = true; mTask.alsOffset = 1.0f; mTask.alsChangingGain = false; mTask.alsSkipSample = false; extiClearPendingGpio(mTask.pin); enableInterrupt(mTask.pin, &mTask.isr, EXTI_TRIGGER_FALLING); return writeRegister(AMS_TMD4903_REG_ENABLE, (POWER_ON_BIT | ALS_ENABLE_BIT | ALS_INT_ENABLE_BIT), SENSOR_STATE_IDLE); } static bool sensorCfgDataAls(void *data, void *cookie) { DEBUG_PRINT("sensorCfgDataAls"); mTask.alsOffset = *(float*)data; INFO_PRINT("Received als cfg data: %d\n", (int)mTask.alsOffset); return true; } 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 the client will get a broadcast event soon if (mTask.lastAlsSample.idata != AMS_TMD4903_ALS_INVALID) { result = osEnqueuePrivateEvt(sensorGetMyEventType(SENS_TYPE_ALS), mTask.lastAlsSample.vptr, NULL, tid); } return result; } static bool sensorPowerProx(bool on, void *cookie) { DEBUG_PRINT("sensorPowerProx: %d\n", on); if (on) { extiClearPendingGpio(mTask.pin); enableInterrupt(mTask.pin, &mTask.isr, EXTI_TRIGGER_FALLING); } else { disableInterrupt(mTask.pin, &mTask.isr); extiClearPendingGpio(mTask.pin); } mTask.lastProxState = PROX_STATE_INIT; mTask.proxOn = on; mTask.proxDirectMode = false; setMode(mTask.alsOn, on, (on) ? SENSOR_STATE_ENABLING_PROX : SENSOR_STATE_DISABLING_PROX); return true; } static bool sensorFirmwareProx(void *cookie) { return sensorSignalInternalEvt(mTask.proxHandle, SENSOR_INTERNAL_EVT_FW_STATE_CHG, 1, 0); } static bool sensorRateProx(uint32_t rate, uint64_t latency, void *cookie) { if (rate == SENSOR_RATE_ONCHANGE) rate = AMS_TMD4903_DEFAULT_RATE; DEBUG_PRINT("sensorRateProx: rate=%ld Hz latency=%lld ns\n", rate/1024, latency); return sensorSignalInternalEvt(mTask.proxHandle, SENSOR_INTERNAL_EVT_RATE_CHG, rate, latency); } static bool sensorFlushProx(void *cookie) { return osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_PROX), SENSOR_DATA_EVENT_FLUSH, NULL); } static bool sensorCalibrateProx(void *cookie) { int16_t failOffsets[4] = {0, 0, 0, 0}; DEBUG_PRINT("sensorCalibrateProx"); if (mTask.alsOn || mTask.proxOn) { INFO_PRINT("cannot calibrate while als or prox are active\n"); sendCalibrationResultProx(SENSOR_APP_EVT_STATUS_BUSY, failOffsets); return false; } mTask.lastProxState = PROX_STATE_INIT; mTask.proxOn = true; mTask.proxCalibrating = true; mTask.proxDirectMode = false; extiClearPendingGpio(mTask.pin); enableInterrupt(mTask.pin, &mTask.isr, EXTI_TRIGGER_FALLING); return writeRegister(AMS_TMD4903_REG_ENABLE, POWER_ON_BIT, SENSOR_STATE_START_PROX_CALIBRATION_0); } static bool sensorCfgDataProx(void *data, void *cookie) { struct AlsProxTransfer *xfer; int32_t *offsets = (int32_t *) data; INFO_PRINT("Received cfg data: {%d, %d, %d, %d}\n", (int)offsets[0], (int)offsets[1], (int)offsets[2], (int)offsets[3]); xfer = allocXfer(SENSOR_STATE_IDLE); if (xfer != NULL) { xfer->txrxBuf[0] = AMS_TMD4903_REG_OFFSETNL; *((int16_t*)&xfer->txrxBuf[1]) = offsets[0]; *((int16_t*)&xfer->txrxBuf[3]) = offsets[1]; *((int16_t*)&xfer->txrxBuf[5]) = offsets[2]; *((int16_t*)&xfer->txrxBuf[7]) = offsets[3]; i2cMasterTx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, 9, i2cCallback, xfer); } return true; } static bool sendLastSampleProx(void *cookie, uint32_t tid) { union EmbeddedDataPoint sample; bool result = true; // See note in sendLastSampleAls if (mTask.lastProxState != PROX_STATE_INIT) { sample.fdata = (mTask.lastProxState == PROX_STATE_NEAR) ? AMS_TMD4903_REPORT_NEAR_VALUE : AMS_TMD4903_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 = sensorCalibrateAls, .sensorCfgData = sensorCfgDataAls, .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 = sensorCalibrateProx, .sensorCfgData = sensorCfgDataProx, .sensorSendOneDirectEvt = sendLastSampleProx }; static void verifySensorId(const struct AlsProxTransfer *xfer) { struct AlsProxTransfer *nextXfer; DEBUG_PRINT("REVID = 0x%02x, ID = 0x%02x\n", xfer->txrxBuf[0], xfer->txrxBuf[1]); // Check the sensor ID if (xfer->err != 0 || xfer->txrxBuf[1] != AMS_TMD4903_ID) { INFO_PRINT("not detected\n"); sensorUnregister(mTask.alsHandle); sensorUnregister(mTask.proxHandle); return; } nextXfer = allocXfer(SENSOR_STATE_INIT_0); if (nextXfer == NULL) { return; } // There is no SW reset on the AMS TMD4903, so we have to reset all registers manually nextXfer->txrxBuf[0] = AMS_TMD4903_REG_ENABLE; nextXfer->txrxBuf[1] = 0x00; // REG_ENABLE - reset value from datasheet nextXfer->txrxBuf[2] = AMS_TMD4903_ATIME_SETTING; // REG_ATIME - 100 ms nextXfer->txrxBuf[3] = AMS_TMD4903_PTIME_SETTING; // REG_PTIME - 50 ms nextXfer->txrxBuf[4] = 0xff; // REG_WTIME - reset value from datasheet nextXfer->txrxBuf[5] = 0x00; // REG_AILTL - reset value from datasheet nextXfer->txrxBuf[6] = 0x00; // REG_AILTH - reset value from datasheet nextXfer->txrxBuf[7] = 0x00; // REG_AIHTL - reset value from datasheet nextXfer->txrxBuf[8] = 0x00; // REG_AIHTH - reset value from datasheet nextXfer->txrxBuf[9] = (AMS_TMD4903_PROX_THRESHOLD_LOW & 0xFF); // REG_PILTL nextXfer->txrxBuf[10] = (AMS_TMD4903_PROX_THRESHOLD_LOW >> 8) & 0xFF; // REG_PILTH nextXfer->txrxBuf[11] = (AMS_TMD4903_PROX_THRESHOLD_HIGH & 0xFF); // REG_PIHTL nextXfer->txrxBuf[12] = (AMS_TMD4903_PROX_THRESHOLD_HIGH >> 8) & 0xFF; // REG_PIHTH nextXfer->txrxBuf[13] = 0x00; // REG_PERS - reset value from datasheet nextXfer->txrxBuf[14] = 0xa0; // REG_CFG0 - reset value from datasheet nextXfer->txrxBuf[15] = AMS_TMD4903_PGCFG0_SETTING; // REG_PGCFG0 nextXfer->txrxBuf[16] = AMS_TMD4903_PGCFG1_SETTING; // REG_PGCFG1 nextXfer->txrxBuf[17] = mTask.alsGain; // REG_CFG1 i2cMasterTx(I2C_BUS_ID, I2C_ADDR, nextXfer->txrxBuf, 18, i2cCallback, nextXfer); } static void handleAlsSample(const struct AlsProxTransfer *xfer) { union EmbeddedDataPoint sample; uint16_t c = *(uint16_t*)(xfer->txrxBuf); uint16_t r = *(uint16_t*)(xfer->txrxBuf+2); uint16_t g = *(uint16_t*)(xfer->txrxBuf+4); uint16_t b = *(uint16_t*)(xfer->txrxBuf+6); if (mTask.alsOn) { sample.fdata = getLuxFromAlsData(c, r, g, b); DEBUG_PRINT("als sample ready: c=%u r=%u g=%u b=%u, gain=%dx, lux=%d\n", c, r, g, b, (int)getAlsGainFromSetting(mTask.alsGain), (int)sample.fdata); if (mTask.alsCalibrating) { sendCalibrationResultAls(SENSOR_APP_EVT_STATUS_SUCCESS, sample.fdata); mTask.alsOn = false; mTask.alsCalibrating = false; writeRegister(AMS_TMD4903_REG_ENABLE, 0, SENSOR_STATE_IDLE); } else if (mTask.alsSkipSample) { mTask.alsSkipSample = false; } else if (!mTask.alsChangingGain) { if (mTask.lastAlsSample.idata != sample.idata) { osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_ALS), sample.vptr, NULL); mTask.lastAlsSample.fdata = sample.fdata; } if (checkForAlsAutoGain(sample.fdata)) { DEBUG_PRINT("Changing ALS gain from %dx to %dx\n", (int)getAlsGainFromSetting(mTask.alsGain), (int)getAlsGainFromSetting(mTask.nextAlsGain)); if (writeRegister(AMS_TMD4903_REG_CFG1, mTask.nextAlsGain, SENSOR_STATE_ALS_CHANGING_GAIN)) { mTask.alsChangingGain = true; } } } } } static void handleProxSample(const struct AlsProxTransfer *xfer) { union EmbeddedDataPoint sample; uint16_t ps = *((uint16_t *) xfer->txrxBuf); uint8_t lastProxState = mTask.lastProxState; DEBUG_PRINT("prox sample ready: prox=%u\n", ps); if (mTask.proxOn) { #if PROX_STREAMING (void)lastProxState; sample.fdata = ps; osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_PROX), sample.vptr, NULL); #else // Lower the bar for "near" threshold so it reports "near" when the prox // value is within the hysteresis threshold if (ps > AMS_TMD4903_PROX_THRESHOLD_LOW) { sample.fdata = AMS_TMD4903_REPORT_NEAR_VALUE; mTask.lastProxState = PROX_STATE_NEAR; } else { sample.fdata = AMS_TMD4903_REPORT_FAR_VALUE; mTask.lastProxState = PROX_STATE_FAR; } if (mTask.lastProxState != lastProxState) osEnqueueEvt(sensorGetMyEventType(SENS_TYPE_PROX), sample.vptr, NULL); #endif #if PROX_STREAMING // reset proximity interrupts writeRegister(AMS_TMD4903_REG_INTCLEAR, 0x60, SENSOR_STATE_IDLE); #else // The TMD4903 direct interrupt mode does not work properly if enabled while something is covering the sensor, // so we need to wait until it is far. if (mTask.lastProxState == PROX_STATE_FAR) { disableInterrupt(mTask.pin, &mTask.isr); extiClearPendingGpio(mTask.pin); // Switch to proximity interrupt direct mode writeRegister(AMS_TMD4903_REG_CFG4, 0x27, SENSOR_STATE_PROX_TRANSITION_0); } else { // If we are in the "near" state, we cannot change to direct interrupt mode, so just clear the interrupt writeRegister(AMS_TMD4903_REG_INTCLEAR, 0x60, SENSOR_STATE_IDLE); } #endif } } /* * Sensor i2c state machine */ static void handle_i2c_event(struct AlsProxTransfer *xfer) { int i; struct AlsProxTransfer *nextXfer; switch (xfer->state) { case SENSOR_STATE_VERIFY_ID: verifySensorId(xfer); break; case SENSOR_STATE_INIT_0: // Set REG_CFG4 to reset value from datasheet writeRegister(AMS_TMD4903_REG_CFG4, 0x07, SENSOR_STATE_INIT_1); break; case SENSOR_STATE_INIT_1: nextXfer = allocXfer(SENSOR_STATE_INIT_2); if (nextXfer != NULL) { nextXfer->txrxBuf[0] = AMS_TMD4903_REG_OFFSETNL; for (i = 0; i < 8; i++) nextXfer->txrxBuf[1+i] = 0x00; i2cMasterTx(I2C_BUS_ID, I2C_ADDR, nextXfer->txrxBuf, 9, i2cCallback, nextXfer); } break; case SENSOR_STATE_INIT_2: // Write REG_INTCLEAR to clear all interrupts writeRegister(AMS_TMD4903_REG_INTCLEAR, 0xFA, SENSOR_STATE_FINISH_INIT); break; case SENSOR_STATE_FINISH_INIT: sensorRegisterInitComplete(mTask.alsHandle); sensorRegisterInitComplete(mTask.proxHandle); break; case SENSOR_STATE_START_PROX_CALIBRATION_0: // Write REG_INTENAB to enable calibration interrupt writeRegister(AMS_TMD4903_REG_INTENAB, CAL_INT_ENABLE_BIT, SENSOR_STATE_START_PROX_CALIBRATION_1); break; case SENSOR_STATE_START_PROX_CALIBRATION_1: // Write REG_CALIB to start calibration writeRegister(AMS_TMD4903_REG_CALIB, 0x01, SENSOR_STATE_IDLE); break; case SENSOR_STATE_FINISH_PROX_CALIBRATION_0: disableInterrupt(mTask.pin, &mTask.isr); extiClearPendingGpio(mTask.pin); mTask.proxOn = false; mTask.proxCalibrating = false; INFO_PRINT("Calibration offsets = {%d, %d, %d, %d}\n", *((int16_t*)&xfer->txrxBuf[0]), *((int16_t*)&xfer->txrxBuf[2]), *((int16_t*)&xfer->txrxBuf[4]), *((int16_t*)&xfer->txrxBuf[6])); // Send calibration result sendCalibrationResultProx(SENSOR_APP_EVT_STATUS_SUCCESS, (int16_t*)xfer->txrxBuf); // Write REG_INTENAB to disable all interrupts writeRegister(AMS_TMD4903_REG_INTENAB, 0x00, SENSOR_STATE_FINISH_PROX_CALIBRATION_1); break; case SENSOR_STATE_FINISH_PROX_CALIBRATION_1: writeRegister(AMS_TMD4903_REG_ENABLE, 0, SENSOR_STATE_IDLE); break; case SENSOR_STATE_ENABLING_ALS: sensorSignalInternalEvt(mTask.alsHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, true, 0); break; case SENSOR_STATE_ENABLING_PROX: sensorSignalInternalEvt(mTask.proxHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, true, 0); break; case SENSOR_STATE_DISABLING_ALS: // Reset AGAIN to 4x mTask.alsGain = ALS_GAIN_4X; writeRegister(AMS_TMD4903_REG_CFG1, mTask.alsGain, SENSOR_STATE_DISABLING_ALS_2); break; case SENSOR_STATE_DISABLING_ALS_2: sensorSignalInternalEvt(mTask.alsHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, false, 0); break; case SENSOR_STATE_DISABLING_PROX: // Write REG_CFG4 to the reset value from datasheet writeRegister(AMS_TMD4903_REG_CFG4, 0x07, SENSOR_STATE_DISABLING_PROX_2); break; case SENSOR_STATE_DISABLING_PROX_2: // Write REG_INTCLEAR to clear proximity interrupts writeRegister(AMS_TMD4903_REG_INTCLEAR, 0x60, SENSOR_STATE_DISABLING_PROX_3); break; case SENSOR_STATE_DISABLING_PROX_3: sensorSignalInternalEvt(mTask.proxHandle, SENSOR_INTERNAL_EVT_POWER_STATE_CHG, false, 0); break; case SENSOR_STATE_ALS_CHANGING_GAIN: if (mTask.alsOn) { mTask.alsChangingGain = false; mTask.alsGain = mTask.nextAlsGain; mTask.alsDebounceSamples = 0; mTask.alsSkipSample = true; } break; case SENSOR_STATE_ALS_SAMPLING: handleAlsSample(xfer); break; case SENSOR_STATE_PROX_SAMPLING: handleProxSample(xfer); break; case SENSOR_STATE_PROX_TRANSITION_0: if (mTask.proxOn) { mTask.proxDirectMode = true; extiClearPendingGpio(mTask.pin); enableInterrupt(mTask.pin, &mTask.isr, EXTI_TRIGGER_BOTH); } break; default: break; } xfer->inUse = false; } /* * Main driver entry points */ static bool init_app(uint32_t myTid) { /* Set up driver private data */ mTask.tid = myTid; mTask.alsOn = false; mTask.proxOn = false; mTask.lastAlsSample.idata = AMS_TMD4903_ALS_INVALID; mTask.lastProxState = PROX_STATE_INIT; mTask.proxCalibrating = false; mTask.alsOffset = 1.0f; mTask.alsGain = ALS_GAIN_4X; mTask.pin = gpioRequest(PROX_INT_PIN); gpioConfigInput(mTask.pin, GPIO_SPEED_LOW, GPIO_PULL_NONE); syscfgSetExtiPort(mTask.pin); mTask.isr.func = proxIsr; mTask.alsHandle = sensorRegister(&sensorInfoAls, &sensorOpsAls, NULL, false); mTask.proxHandle = sensorRegister(&sensorInfoProx, &sensorOpsProx, NULL, false); osEventSubscribe(myTid, EVT_APP_START); return true; } static void end_app(void) { disableInterrupt(mTask.pin, &mTask.isr); extiUnchainIsr(PROX_IRQ, &mTask.isr); extiClearPendingGpio(mTask.pin); gpioRelease(mTask.pin); sensorUnregister(mTask.alsHandle); sensorUnregister(mTask.proxHandle); i2cMasterRelease(I2C_BUS_ID); } static void handle_event(uint32_t evtType, const void* evtData) { struct AlsProxTransfer *xfer; switch (evtType) { case EVT_APP_START: i2cMasterRequest(I2C_BUS_ID, I2C_SPEED); // Read the ID xfer = allocXfer(SENSOR_STATE_VERIFY_ID); if (xfer != NULL) { xfer->txrxBuf[0] = AMS_TMD4903_REG_REVID; i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, 1, xfer->txrxBuf, 2, i2cCallback, xfer); } break; case EVT_SENSOR_I2C: // Dropping const here (we own this memory) handle_i2c_event((struct AlsProxTransfer *) evtData); break; case EVT_SENSOR_ALS_INTERRUPT: disableInterrupt(mTask.pin, &mTask.isr); extiClearPendingGpio(mTask.pin); // NOTE: fall-through to initiate read of ALS data registers case EVT_SENSOR_ALS_TIMER: xfer = allocXfer(SENSOR_STATE_ALS_SAMPLING); if (xfer != NULL) { xfer->txrxBuf[0] = AMS_TMD4903_REG_CDATAL; i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, 1, xfer->txrxBuf, 8, i2cCallback, xfer); } break; case EVT_SENSOR_PROX_INTERRUPT: xfer = allocXfer(SENSOR_STATE_PROX_SAMPLING); if (xfer != NULL) { if (mTask.proxCalibrating) { xfer->txrxBuf[0] = AMS_TMD4903_REG_OFFSETNL; xfer->state = SENSOR_STATE_FINISH_PROX_CALIBRATION_0; i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, 1, xfer->txrxBuf, 8, i2cCallback, xfer); } else { xfer->txrxBuf[0] = AMS_TMD4903_REG_PDATAL; i2cMasterTxRx(I2C_BUS_ID, I2C_ADDR, xfer->txrxBuf, 1, xfer->txrxBuf, 2, i2cCallback, xfer); } } break; } } INTERNAL_APP_INIT(AMS_TMD4903_APP_ID, AMS_TMD4903_APP_VERSION, init_app, end_app, handle_event);