dc8c814ca3
Fixed coding standard error in several files. Use of while( is incorrect; a space is required between while and (. Also ran tools/nxstyle and fix thoses complaints as well in most files. Changes to comply with coding standard. Mostly focused on files with missing space after keyword in if(, switch(, and for(. Offending files also got changes to comply with tools nxstyle. If there were logs of nxstyle complaints, the file also got a taste of tools/indent.sh. Still need to fix occurrences of while( with missing space. There are a lot of them.
1346 lines
38 KiB
C
1346 lines
38 KiB
C
/****************************************************************************
|
|
* drivers/sensors/apds9960.c
|
|
* Character driver for the APDS9960 Gesture Sensor
|
|
*
|
|
* Copyright (C) 2017 Alan Carvalho de Assis. All rights reserved.
|
|
* Author: Alan Carvalho de Assis <acassis@gmail.com>
|
|
*
|
|
* This driver is based on APDS-9960 Arduino library developed by
|
|
* Shawn Hymel from SparkFun Electronics and released under public
|
|
* domain.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/signal.h>
|
|
#include <nuttx/random.h>
|
|
#include <nuttx/wqueue.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/i2c/i2c_master.h>
|
|
#include <nuttx/sensors/apds9960.h>
|
|
|
|
#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_APDS9960)
|
|
|
|
/****************************************************************************
|
|
* Pre-process Definitions
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_APDS9960_I2C_FREQUENCY
|
|
# define CONFIG_APDS9960_I2C_FREQUENCY 400000
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct apds9960_dev_s
|
|
{
|
|
FAR struct apds9960_config_s *config; /* Hardware Configuration */
|
|
struct work_s work; /* Supports ISR "bottom half" */
|
|
struct gesture_data_s gesture_data; /* Gesture data container */
|
|
int gesture_ud_delta; /* UP/DOWN delta */
|
|
int gesture_lr_delta; /* LEFT/RIGHT delta */
|
|
int gesture_ud_count; /* UP/DOWN counter */
|
|
int gesture_lr_count; /* LEFT/RIGHT counter */
|
|
int gesture_near_count; /* Near distance counter */
|
|
int gesture_far_count; /* Far distance counter */
|
|
int gesture_state; /* Gesture machine state */
|
|
int gesture_motion; /* Gesture motion direction */
|
|
sem_t sample_sem; /* Semaphore for sample data */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* Reset gesture values */
|
|
|
|
static void apds9960_resetgesture(FAR struct apds9960_dev_s *priv);
|
|
|
|
/* Setup default initial values */
|
|
|
|
static int apds9960_setdefault(FAR struct apds9960_dev_s *priv);
|
|
|
|
/* Probe function to verify if sensor is present */
|
|
|
|
static int apds9960_probe(FAR struct apds9960_dev_s *priv);
|
|
|
|
/* Work queue */
|
|
|
|
static void apds9960_worker(FAR void *arg);
|
|
|
|
/* Gesture processing/decoding functions */
|
|
|
|
static int apds9960_readgesture(FAR struct apds9960_dev_s *priv);
|
|
static bool apds9960_decodegesture(FAR struct apds9960_dev_s *priv);
|
|
static bool apds9960_processgesture(FAR struct apds9960_dev_s *priv);
|
|
static bool apds9960_isgestureavailable(FAR struct apds9960_dev_s *priv);
|
|
|
|
/* I2C Helpers */
|
|
|
|
static int apds9960_i2c_read(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, FAR uint8_t *regval, int len);
|
|
static int apds9960_i2c_read8(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, FAR uint8_t *regval);
|
|
static int apds9960_i2c_write(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const *data, int len);
|
|
static int apds9960_i2c_write8(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, uint8_t regval);
|
|
|
|
/* Character driver methods */
|
|
|
|
static int apds9960_open(FAR struct file *filep);
|
|
static int apds9960_close(FAR struct file *filep);
|
|
static ssize_t apds9960_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t apds9960_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t buflen);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct file_operations g_apds9960_fops =
|
|
{
|
|
apds9960_open, /* open */
|
|
apds9960_close, /* close */
|
|
apds9960_read, /* read */
|
|
apds9960_write, /* write */
|
|
NULL, /* seek */
|
|
NULL /* ioctl */
|
|
#ifndef CONFIG_DISABLE_POLL
|
|
, NULL /* poll */
|
|
#endif
|
|
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
|
|
, NULL /* unlink */
|
|
#endif
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_worker
|
|
****************************************************************************/
|
|
|
|
static void apds9960_worker(FAR void *arg)
|
|
{
|
|
FAR struct apds9960_dev_s *priv = (FAR struct apds9960_dev_s *)arg;
|
|
int ret;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
ret = apds9960_readgesture(priv);
|
|
if (ret != DIR_NONE)
|
|
{
|
|
sninfo("Got a valid gesture!\n");
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_int_handler
|
|
*
|
|
* Description:
|
|
* Interrupt handler (ISR) for APDS-99600 INT pin.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_int_handler(int irq, FAR void *context, FAR void *arg)
|
|
{
|
|
int ret;
|
|
|
|
FAR struct apds9960_dev_s *priv = (FAR struct apds9960_dev_s *)arg;
|
|
|
|
DEBUGASSERT(priv != NULL);
|
|
|
|
/* Transfer processing to the worker thread. Since APDS-9960 interrupts
|
|
* are disabled while the work is pending, no special action should be
|
|
* required to protect the work queue.
|
|
*/
|
|
|
|
DEBUGASSERT(priv->work.worker == NULL);
|
|
ret = work_queue(HPWORK, &priv->work, apds9960_worker, priv, 0);
|
|
if (ret != 0)
|
|
{
|
|
snerr("ERROR: Failed to queue work: %d\n", ret);
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_resetgesture
|
|
*
|
|
* Description:
|
|
* Reset gesture values
|
|
*
|
|
****************************************************************************/
|
|
|
|
static void apds9960_resetgesture(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
priv->gesture_data.index = 0;
|
|
priv->gesture_data.total_gestures = 0;
|
|
|
|
priv->gesture_ud_delta = 0;
|
|
priv->gesture_lr_delta = 0;
|
|
|
|
priv->gesture_ud_count = 0;
|
|
priv->gesture_lr_count = 0;
|
|
|
|
priv->gesture_near_count = 0;
|
|
priv->gesture_far_count = 0;
|
|
|
|
priv->gesture_state = 0;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_setdefault
|
|
*
|
|
* Description:
|
|
* Verify if sensor is present. Check if ID is 0xAB.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_setdefault(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
int ret;
|
|
|
|
/* Set default values for ambient light and proximity registers */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_ATIME, DEFAULT_ATIME);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_ATIME!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_WTIME, DEFAULT_WTIME);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_WTIME!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_PPULSE, DEFAULT_PPULSE);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_PPULSE!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_POFFSET_UR, DEFAULT_POFFSET_UR);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_POFFSET_UR!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_POFFSET_DL, DEFAULT_POFFSET_DL);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_POFFSET_DL!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_CONFIG1, DEFAULT_CONFIG1);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_CONFIG1!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Set LED driver strength to 100mA, AGAIN 4X and PGAIN 4X */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_CONTROL, DEFAULT_CONTROL);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_CONTROL!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_PILT, DEFAULT_PILT);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_PILT!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_PIHT, DEFAULT_PIHT);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_PIHT!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_AILTL, DEFAULT_AILTL);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_AILTL!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_AILTH, DEFAULT_AILTH);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_AILTH!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_AIHTL, DEFAULT_AIHTL);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_AIHTL!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_AIHTH, DEFAULT_AIHTH);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_AIHTH!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_PERS, DEFAULT_PERS);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_PERS!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_CONFIG2, DEFAULT_CONFIG2);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_CONFIG2!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_CONFIG3, DEFAULT_CONFIG3);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_CONFIG3!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GPENTH, DEFAULT_GPENTH);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GPENTH!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GEXTH, DEFAULT_GEXTH);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GEXTH!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG1, DEFAULT_GCONFIG1);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG1!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG2, DEFAULT_GCONFIG2);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG2!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_U, DEFAULT_GOFFSET_U);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GOFFSET_U!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_D, DEFAULT_GOFFSET_D);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GOFFSET_D!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_L, DEFAULT_GOFFSET_L);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GOFFSET_L!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GOFFSET_R, DEFAULT_GOFFSET_R);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GOFFSET_R!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GPULSE, DEFAULT_GPULSE);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GPULSE!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG3, DEFAULT_GCONFIG3);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG3!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG4, DEFAULT_GCONFIG4);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG3!\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_WTIME, 0xff);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_WTIME!\n");
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_probe
|
|
*
|
|
* Description:
|
|
* Verify if sensor is present. Check if ID is 0xAB.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_probe(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
int ret;
|
|
uint8_t id;
|
|
|
|
ret = apds9960_i2c_read8(priv, APDS9960_ID, &id);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
if (id != APDS9960_ID_VAL)
|
|
{
|
|
return -ENODEV;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_i2c_read
|
|
*
|
|
* Description:
|
|
* Read an arbitrary number of bytes starting at regaddr
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_i2c_read(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, FAR uint8_t *regval, int len)
|
|
{
|
|
struct i2c_config_s config;
|
|
int ret = -1;
|
|
|
|
/* Set up the I2C configuration */
|
|
|
|
config.frequency = CONFIG_APDS9960_I2C_FREQUENCY;
|
|
config.address = priv->config->i2c_addr;
|
|
config.addrlen = 7;
|
|
|
|
/* Write the register address to read from */
|
|
|
|
ret = i2c_write(priv->config->i2c_dev, &config, ®addr, 1);
|
|
if (ret < 0)
|
|
{
|
|
snerr ("i2c_write failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/* Read "len" bytes from regaddr */
|
|
|
|
ret = i2c_read(priv->config->i2c_dev, &config, regval, len);
|
|
if (ret < 0)
|
|
{
|
|
snerr ("i2c_read failed: %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_i2c_read8
|
|
*
|
|
* Description:
|
|
* Read 8-bit register
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_i2c_read8(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, FAR uint8_t *regval)
|
|
{
|
|
int ret;
|
|
|
|
ret = apds9960_i2c_read(priv, regaddr, regval, 1);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_i2c_write
|
|
*
|
|
* Description:
|
|
* Write an arbitrary number of bytes starting at regaddr.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_i2c_write(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const *data, int len)
|
|
{
|
|
struct i2c_config_s config;
|
|
int ret;
|
|
|
|
/* Set up the I2C configuration */
|
|
|
|
config.frequency = CONFIG_APDS9960_I2C_FREQUENCY;
|
|
config.address = priv->config->i2c_addr;
|
|
config.addrlen = 7;
|
|
|
|
/* Write the data */
|
|
|
|
ret = i2c_write(priv->config->i2c_dev, &config, data, len);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: i2c_write failed: %d\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_i2c_write8
|
|
*
|
|
* Description:
|
|
* Write an arbitrary number of bytes starting at regaddr.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_i2c_write8(FAR struct apds9960_dev_s *priv,
|
|
uint8_t const regaddr, uint8_t regval)
|
|
{
|
|
int ret;
|
|
uint8_t data[2];
|
|
|
|
/* Create the addr:val data */
|
|
|
|
data[0] = regaddr;
|
|
data[1] = regval;
|
|
|
|
ret = apds9960_i2c_write(priv, data, 2);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_isgestureavailable
|
|
*
|
|
* Description:
|
|
* Return true is gesture data is valid.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool apds9960_isgestureavailable(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
int ret;
|
|
uint8_t val;
|
|
|
|
/* Read value from GSTATUS register */
|
|
|
|
ret = apds9960_i2c_read8(priv, APDS9960_GSTATUS, &val);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to read APDS9960_GSTATUS!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Return true/false based on GVALID bit */
|
|
|
|
if ((val & GVALID) == GVALID)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_processgesture
|
|
*
|
|
* Description:
|
|
* Process the data read from the photodiodes
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool apds9960_processgesture(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
uint8_t u_first = 0;
|
|
uint8_t d_first = 0;
|
|
uint8_t l_first = 0;
|
|
uint8_t r_first = 0;
|
|
uint8_t u_last = 0;
|
|
uint8_t d_last = 0;
|
|
uint8_t l_last = 0;
|
|
uint8_t r_last = 0;
|
|
int ud_ratio_first;
|
|
int lr_ratio_first;
|
|
int ud_ratio_last;
|
|
int lr_ratio_last;
|
|
int ud_delta;
|
|
int lr_delta;
|
|
int i;
|
|
|
|
/* If we have less than 4 total gestures, that's not enough */
|
|
|
|
if (priv->gesture_data.total_gestures <= 4)
|
|
{
|
|
snerr("ERROR: We don't have enough gesture: %d\n",
|
|
priv->gesture_data.total_gestures);
|
|
return false;
|
|
}
|
|
|
|
/* Check to make sure our data isn't out of bounds */
|
|
|
|
if ((priv->gesture_data.total_gestures <= 32) && \
|
|
(priv->gesture_data.total_gestures > 0))
|
|
{
|
|
/* Find the first value in U/D/L/R above the threshold */
|
|
|
|
for (i = 0; i < priv->gesture_data.total_gestures; i++)
|
|
{
|
|
if ((priv->gesture_data.u_data[i] > GESTURE_THRESHOLD_OUT) && \
|
|
(priv->gesture_data.d_data[i] > GESTURE_THRESHOLD_OUT) && \
|
|
(priv->gesture_data.l_data[i] > GESTURE_THRESHOLD_OUT) && \
|
|
(priv->gesture_data.r_data[i] > GESTURE_THRESHOLD_OUT))
|
|
{
|
|
u_first = priv->gesture_data.u_data[i];
|
|
d_first = priv->gesture_data.d_data[i];
|
|
l_first = priv->gesture_data.l_data[i];
|
|
r_first = priv->gesture_data.r_data[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* If one of the _first values is 0, then there is no good data */
|
|
|
|
if ((u_first == 0) || (d_first == 0) || \
|
|
(l_first == 0) || (r_first == 0))
|
|
{
|
|
snerr("ERROR: First value is zero! U=%d, D=%d, L=%d, R=%d\n", \
|
|
u_first, d_first, l_first, r_first);
|
|
return false;
|
|
}
|
|
|
|
/* Find the last value in U/D/L/R above the threshold */
|
|
|
|
for (i = priv->gesture_data.total_gestures - 1; i >= 0; i--)
|
|
{
|
|
sninfo("Finding last: \n");
|
|
sninfo("U: %03d\n", priv->gesture_data.u_data[i]);
|
|
sninfo("D: %03d\n", priv->gesture_data.d_data[i]);
|
|
sninfo("L: %03d\n", priv->gesture_data.l_data[i]);
|
|
sninfo("R: %03d\n", priv->gesture_data.r_data[i]);
|
|
|
|
if ((priv->gesture_data.u_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(priv->gesture_data.d_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(priv->gesture_data.l_data[i] > GESTURE_THRESHOLD_OUT) &&
|
|
(priv->gesture_data.r_data[i] > GESTURE_THRESHOLD_OUT))
|
|
{
|
|
u_last = priv->gesture_data.u_data[i];
|
|
d_last = priv->gesture_data.d_data[i];
|
|
l_last = priv->gesture_data.l_data[i];
|
|
r_last = priv->gesture_data.r_data[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Calculate the first vs. last ratio of up/down and left/right */
|
|
|
|
ud_ratio_first = ((u_first - d_first) * 100) / (u_first + d_first);
|
|
lr_ratio_first = ((l_first - r_first) * 100) / (l_first + r_first);
|
|
ud_ratio_last = ((u_last - d_last) * 100) / (u_last + d_last);
|
|
lr_ratio_last = ((l_last - r_last) * 100) / (l_last + r_last);
|
|
|
|
sninfo("Last Values: \n");
|
|
sninfo("U: %03d\n", u_last);
|
|
sninfo("D: %03d\n", d_last);
|
|
sninfo("L: %03d\n", l_last);
|
|
sninfo("R: %03d\n", r_last);
|
|
|
|
sninfo("Ratios: \n");
|
|
sninfo("UD Fi: %03d\n", ud_ratio_first);
|
|
sninfo("UD La: %03d\n", ud_ratio_last);
|
|
sninfo("LR Fi: %03d\n", lr_ratio_first);
|
|
sninfo("LR La: %03d\n", lr_ratio_last);
|
|
|
|
/* Determine the difference between the first and last ratios */
|
|
|
|
ud_delta = ud_ratio_last - ud_ratio_first;
|
|
lr_delta = lr_ratio_last - lr_ratio_first;
|
|
|
|
sninfo("Deltas: \n");
|
|
sninfo("UD: %03d\n", ud_delta);
|
|
sninfo("LR: %03d\n", lr_delta);
|
|
|
|
/* Accumulate the UD and LR delta values */
|
|
|
|
priv->gesture_ud_delta += ud_delta;
|
|
priv->gesture_lr_delta += lr_delta;
|
|
|
|
sninfo("Accumulations: \n");
|
|
sninfo("UD: %03d\n", priv->gesture_ud_delta);
|
|
sninfo("LR: %03d\n", priv->gesture_lr_delta);
|
|
|
|
/* Determine U/D gesture */
|
|
|
|
if (priv->gesture_ud_delta >= GESTURE_SENSITIVITY_1)
|
|
{
|
|
priv->gesture_ud_count = 1;
|
|
}
|
|
else
|
|
{
|
|
if (priv->gesture_ud_delta <= -GESTURE_SENSITIVITY_1)
|
|
{
|
|
priv->gesture_ud_count = -1;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_ud_count = 0;
|
|
}
|
|
}
|
|
|
|
/* Determine L/R gesture */
|
|
|
|
if (priv->gesture_lr_delta >= GESTURE_SENSITIVITY_1)
|
|
{
|
|
priv->gesture_lr_count = 1;
|
|
}
|
|
else
|
|
{
|
|
if (priv->gesture_lr_delta <= -GESTURE_SENSITIVITY_1)
|
|
{
|
|
priv->gesture_lr_count = -1;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_lr_count = 0;
|
|
}
|
|
}
|
|
|
|
/* Determine Near/Far gesture */
|
|
|
|
if ((priv->gesture_ud_count == 0) && (priv->gesture_lr_count == 0))
|
|
{
|
|
if ((abs(ud_delta) < GESTURE_SENSITIVITY_2) && \
|
|
(abs(lr_delta) < GESTURE_SENSITIVITY_2))
|
|
{
|
|
if ((ud_delta == 0) && (lr_delta == 0))
|
|
{
|
|
priv->gesture_near_count++;
|
|
}
|
|
else
|
|
{
|
|
if ((ud_delta != 0) || (lr_delta != 0))
|
|
{
|
|
priv->gesture_far_count++;
|
|
}
|
|
}
|
|
|
|
if ((priv->gesture_near_count >= 10) && \
|
|
(priv->gesture_far_count >= 2))
|
|
{
|
|
if ((ud_delta == 0) && (lr_delta == 0))
|
|
{
|
|
priv->gesture_state = NEAR_STATE;
|
|
}
|
|
else
|
|
{
|
|
if ((ud_delta != 0) && (lr_delta != 0))
|
|
{
|
|
priv->gesture_state = FAR_STATE;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((abs(ud_delta) < GESTURE_SENSITIVITY_2) && \
|
|
(abs(lr_delta) < GESTURE_SENSITIVITY_2))
|
|
{
|
|
if ((ud_delta == 0) && (lr_delta == 0))
|
|
{
|
|
priv->gesture_near_count++;
|
|
}
|
|
|
|
if (priv->gesture_near_count >= 10)
|
|
{
|
|
priv->gesture_ud_count = 0;
|
|
priv->gesture_lr_count = 0;
|
|
priv->gesture_ud_delta = 0;
|
|
priv->gesture_lr_delta = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
sninfo(" UD_CT: %03d\n", priv->gesture_ud_count);
|
|
sninfo(" LR_CT: %03d\n", priv->gesture_lr_count);
|
|
sninfo(" NEAR_CT: %03d\n", priv->gesture_near_count);
|
|
sninfo(" FAR_CT: %03d\n", priv->gesture_far_count);
|
|
sninfo("----------------------\n");
|
|
|
|
return false;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_decodegesture
|
|
*
|
|
* Description:
|
|
* Decode the sensor data and return true if there is some valid data
|
|
*
|
|
****************************************************************************/
|
|
|
|
static bool apds9960_decodegesture(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
/* Return if near or far event is detected */
|
|
|
|
if (priv->gesture_state == NEAR_STATE)
|
|
{
|
|
priv->gesture_motion = DIR_NEAR;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (priv->gesture_state == FAR_STATE)
|
|
{
|
|
priv->gesture_motion = DIR_FAR;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Determine swipe direction */
|
|
|
|
if ((priv->gesture_ud_count == -1) && (priv->gesture_lr_count == 0))
|
|
{
|
|
priv->gesture_motion = DIR_UP;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 1) && (priv->gesture_lr_count == 0))
|
|
{
|
|
priv->gesture_motion = DIR_DOWN;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 0) && (priv->gesture_lr_count == 1))
|
|
{
|
|
priv->gesture_motion = DIR_RIGHT;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 0) &&
|
|
(priv->gesture_lr_count == -1))
|
|
{
|
|
priv->gesture_motion = DIR_LEFT;
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == -1) &&
|
|
(priv->gesture_lr_count == 1))
|
|
{
|
|
if (abs(priv->gesture_ud_delta) > \
|
|
abs(priv->gesture_lr_delta))
|
|
{
|
|
priv->gesture_motion = DIR_UP;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_motion = DIR_RIGHT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 1) && \
|
|
(priv->gesture_lr_count == -1))
|
|
{
|
|
if (abs(priv->gesture_ud_delta) > \
|
|
abs(priv->gesture_lr_delta))
|
|
{
|
|
priv->gesture_motion = DIR_DOWN;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_motion = DIR_LEFT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == -1) && \
|
|
(priv->gesture_lr_count == -1))
|
|
{
|
|
if (abs(priv->gesture_ud_delta) > \
|
|
abs(priv->gesture_lr_delta))
|
|
{
|
|
priv->gesture_motion = DIR_UP;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_motion = DIR_LEFT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ((priv->gesture_ud_count == 1) && \
|
|
(priv->gesture_lr_count == 1))
|
|
{
|
|
if (abs(priv->gesture_ud_delta) > \
|
|
abs(priv->gesture_lr_delta))
|
|
{
|
|
priv->gesture_motion = DIR_DOWN;
|
|
}
|
|
else
|
|
{
|
|
priv->gesture_motion = DIR_RIGHT;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_readgesture
|
|
*
|
|
* Description:
|
|
* Read the photodiode data, process/decode it and return the guess
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_readgesture(FAR struct apds9960_dev_s *priv)
|
|
{
|
|
uint8_t fifo_level = 0;
|
|
uint8_t bytes_read = 0;
|
|
uint8_t fifo_data[128];
|
|
uint8_t gstatus;
|
|
int motion;
|
|
int ret;
|
|
int i;
|
|
|
|
/* Make sure that power and gesture is on and data is valid */
|
|
|
|
if (!apds9960_isgestureavailable(priv))
|
|
{
|
|
return DIR_NONE;
|
|
}
|
|
|
|
/* Keep looping as long as gesture data is valid */
|
|
|
|
while (1)
|
|
{
|
|
/* Wait some time to collect next batch of FIFO data */
|
|
|
|
nxsig_usleep(FIFO_PAUSE_TIME);
|
|
|
|
/* Get the contents of the STATUS register. Is data still valid? */
|
|
|
|
ret = apds9960_i2c_read8(priv, APDS9960_GSTATUS, &gstatus);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to read APDS9960_GSTATUS!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* If we have valid data, read in FIFO */
|
|
|
|
if ((gstatus & GVALID) == GVALID)
|
|
{
|
|
/* Read the current FIFO level */
|
|
|
|
ret = apds9960_i2c_read8(priv, APDS9960_GFLVL, &fifo_level);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to read APDS9960_GFLVL!\n");
|
|
return ret;
|
|
}
|
|
|
|
sninfo("FIFO Level: %d\n", fifo_level);
|
|
|
|
/* If there's stuff in the FIFO, read it into our data block */
|
|
|
|
if (fifo_level > 0)
|
|
{
|
|
bytes_read = fifo_level * 4;
|
|
ret = apds9960_i2c_read(priv, APDS9960_GFIFO_U,
|
|
(uint8_t *) fifo_data, bytes_read);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to read APDS9960_GFIFO_U!\n");
|
|
return ret;
|
|
}
|
|
|
|
sninfo("\nFIFO Dump:\n");
|
|
for (i = 0; i < fifo_level; i++)
|
|
{
|
|
sninfo("U: %03d | D: %03d | L: %03d | R: %03d\n",
|
|
fifo_data[i], fifo_data[i+1],
|
|
fifo_data[i+2], fifo_data[i+3]);
|
|
}
|
|
|
|
sninfo("\n");
|
|
|
|
/* If at least 1 set of data, sort the data into U/D/L/R */
|
|
|
|
if (bytes_read >= 4)
|
|
{
|
|
for (i = 0; i < bytes_read; i += 4)
|
|
{
|
|
priv->gesture_data.u_data[priv->gesture_data.index] = fifo_data[i + 0];
|
|
priv->gesture_data.d_data[priv->gesture_data.index] = fifo_data[i + 1];
|
|
priv->gesture_data.l_data[priv->gesture_data.index] = fifo_data[i + 2];
|
|
priv->gesture_data.r_data[priv->gesture_data.index] = fifo_data[i + 3];
|
|
priv->gesture_data.index++;
|
|
priv->gesture_data.total_gestures++;
|
|
}
|
|
|
|
sninfo("Up Data:\n");
|
|
for (i = 0; i < priv->gesture_data.total_gestures; i++)
|
|
{
|
|
sninfo("%03d\n", priv->gesture_data.u_data[i]);
|
|
}
|
|
|
|
sninfo("\n");
|
|
|
|
/* Filter and process gesture data. Decode near/far state */
|
|
|
|
if (apds9960_processgesture(priv))
|
|
{
|
|
if (apds9960_decodegesture(priv))
|
|
{
|
|
/* TODO: U-Turn Gestures */
|
|
}
|
|
}
|
|
|
|
/* Reset data */
|
|
|
|
priv->gesture_data.index = 0;
|
|
priv->gesture_data.total_gestures = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Determine best guessed gesture and clean up */
|
|
|
|
nxsig_usleep(FIFO_PAUSE_TIME);
|
|
apds9960_decodegesture(priv);
|
|
motion = priv->gesture_motion;
|
|
|
|
snwarn("END: %d\n", priv->gesture_motion);
|
|
|
|
if (motion == DIR_LEFT)
|
|
{
|
|
snwarn("RESULT = LEFT\n");
|
|
}
|
|
|
|
if (motion == DIR_RIGHT)
|
|
{
|
|
snwarn("RESULT = RIGHT\n");
|
|
}
|
|
|
|
if (motion == DIR_UP)
|
|
{
|
|
snwarn("RESULT = UP\n");
|
|
}
|
|
|
|
if (motion == DIR_DOWN)
|
|
{
|
|
snwarn("RESULT = DOWN\n");
|
|
}
|
|
|
|
/* Increase semaphore to indicate new data */
|
|
|
|
nxsem_post(&priv->sample_sem);
|
|
|
|
apds9960_resetgesture(priv);
|
|
return motion;
|
|
}
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_open
|
|
*
|
|
* Description:
|
|
* This function is called whenever the APDS9960 device is opened.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_open(FAR struct file *filep)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_close
|
|
*
|
|
* Description:
|
|
* This routine is called when the APDS9960 device is closed.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int apds9960_close(FAR struct file *filep)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t apds9960_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct inode *inode;
|
|
FAR struct apds9960_dev_s *priv;
|
|
int ret;
|
|
|
|
DEBUGASSERT(filep);
|
|
inode = filep->f_inode;
|
|
|
|
DEBUGASSERT(inode && inode->i_private);
|
|
priv = (FAR struct apds9960_dev_s *)inode->i_private;
|
|
|
|
/* Check if the user is reading the right size */
|
|
|
|
if (buflen < 1)
|
|
{
|
|
snerr("ERROR: You need to read at least 1 byte from this sensor!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Wait for data available */
|
|
|
|
do
|
|
{
|
|
ret = nxsem_wait(&priv->sample_sem);
|
|
|
|
/* The only case that an error should occur here is if the wait was
|
|
* awakened by a signal.
|
|
*/
|
|
|
|
DEBUGASSERT(ret == OK || ret == -EINTR);
|
|
}
|
|
while (ret == -EINTR);
|
|
|
|
buffer[0] = (char) priv->gesture_motion;
|
|
buflen = 1;
|
|
|
|
return buflen;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t apds9960_write(FAR struct file *filep,
|
|
FAR const char *buffer, size_t buflen)
|
|
{
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: apds9960_register
|
|
*
|
|
* Description:
|
|
* Register the APDS9960 character device as 'devpath'
|
|
*
|
|
* Input Parameters:
|
|
* devpath - The full path to the driver to register. E.g., "/dev/gest0"
|
|
* i2c - An instance of the I2C interface to use to communicate with APDS9960
|
|
* addr - The I2C address of the APDS9960.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
int apds9960_register(FAR const char *devpath,
|
|
FAR struct apds9960_config_s *config)
|
|
{
|
|
int ret;
|
|
|
|
/* Sanity check */
|
|
|
|
DEBUGASSERT(i2c != NULL);
|
|
|
|
/* Initialize the APDS9960 device structure */
|
|
|
|
FAR struct apds9960_dev_s *priv =
|
|
(FAR struct apds9960_dev_s *)kmm_malloc(sizeof(struct apds9960_dev_s));
|
|
|
|
if (priv == NULL)
|
|
{
|
|
snerr("ERROR: Failed to allocate instance\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
priv->config = config;
|
|
priv->work.worker = NULL;
|
|
priv->gesture_motion = DIR_NONE;
|
|
nxsem_init(&priv->sample_sem, 0, 0);
|
|
|
|
/* Probe APDS9960 device */
|
|
|
|
ret = apds9960_probe(priv);
|
|
if (ret != OK)
|
|
{
|
|
snerr("ERROR: APDS-9960 is not responding!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Turn the device OFF to make it sane */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, 0);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Wait 100ms */
|
|
|
|
nxsig_usleep(100000);
|
|
|
|
/* Initialize the device (leave RESET) */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, PON);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Set default initial register values */
|
|
|
|
ret = apds9960_setdefault(priv);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Reset gesture values */
|
|
|
|
apds9960_resetgesture(priv);
|
|
|
|
/* Enable the Gesture mode and interruptions */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_GCONFIG4, (GMODE | GIEN));
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to write APDS9960_GCONFIG4!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Enable the Gesture mode (Proximity mode is needed for gesture mode) */
|
|
|
|
ret = apds9960_i2c_write8(priv, APDS9960_ENABLE, PON | PEN | GEN | WEN);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to initialize the APDS9960!\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Register the character driver */
|
|
|
|
ret = register_driver(devpath, &g_apds9960_fops, 0666, priv);
|
|
if (ret < 0)
|
|
{
|
|
snerr("ERROR: Failed to register driver: %d\n", ret);
|
|
kmm_free(priv);
|
|
}
|
|
|
|
/* Attach to the interrupt */
|
|
|
|
priv->config->irq_attach(priv->config, apds9960_int_handler, priv);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#endif /* CONFIG_I2C && CONFIG_SENSORS_APDS9960 */
|