| /* |
| * 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); |