nuttx/drivers/sensors/kxtj9.c
Gregory Nutt 936df1bcb5 Adds new OS internal functions nxsig_sleep() and nxsig_usleep. These differ from the standard sleep() and usleep() in that (1) they don't cause cancellation points, and (2) don't set the errno variable (if applicable). All calls to sleep() and usleep() changed to calls to nxsig_sleep() and nxsig_usleep().
Squashed commit of the following:

    Change all calls to usleep() in the OS proper to calls to nxsig_usleep()

    sched/signal:  Add a new OS internal function nxsig_usleep() that is functionally equivalent to usleep() but does not cause a cancellaption point and does not modify the errno variable.

    sched/signal:  Add a new OS internal function nxsig_sleep() that is functionally equivalent to sleep() but does not cause a cancellaption point.
2017-10-06 10:15:01 -06:00

690 lines
18 KiB
C

/****************************************************************************
* drivers/sensors/kxtj9.c
*
* Copyright (C) 2016-2017 Gregory Nutt. All rights reserved.
* Author: Gregory Nutt <gnutt@nuttx.org>
*
* This driver derives from the Motorola Moto Z MDK:
*
* Copyright (c) 2016 Motorola Mobility, LLC.
* All rights reserved.
*
* 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 of the copyright holder 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 HOLDER 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 <stdlib.h>
#include <semaphore.h>
#include <errno.h>
#include <debug.h>
#include <nuttx/kmalloc.h>
#include <nuttx/signal.h>
#include <nuttx/fs/fs.h>
#include <nuttx/i2c/i2c_master.h>
#include <nuttx/sensors/kxtj9.h>
#include <nuttx/random.h>
#if defined(CONFIG_I2C) && defined(CONFIG_SENSORS_KXTJ9)
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_KXTJ9_I2C_BUS_SPEED
# define CONFIG_KXTJ9_I2C_BUS_SPEED 400000
#endif
/* Register Definitions *****************************************************/
/* Output registers */
#define XOUT_L 0x06
#define WHO_AM_I 0x0f
#define DCST_RESP 0x0c
/* Control registers */
#define INT_REL 0x1a
#define CTRL_REG1 0x1b
#define INT_CTRL1 0x1e
#define DATA_CTRL 0x21
#define CTRL_REG2 0x1d
/* Control register 1 bits */
#define PC1_OFF 0x7f
#define PC1_ON (1 << 7)
/* CTRL_REG1: set resolution, g-range, data ready enable */
/* Output resolution: 8-bit valid or 12-bit valid */
#define RES_8BIT 0
#define RES_12BIT (1 << 6)
/* Data ready funtion enable bit: set during probe if using irq mode */
#define DRDYE (1 << 5)
/* Output g-range: +/-2g, 4g, or 8g */
#define KXTJ9_G_2G 0
#define KXTJ9_G_4G (1 << 3)
#define KXTJ9_G_8G (1 << 4)
/* Interrupt control register 1 bits */
/* Set these during probe if using irq mode */
#define KXTJ9_IEL (1 << 3)
#define KXTJ9_IEA (1 << 4)
#define KXTJ9_IEN (1 << 5)
#define KXTJ9_SRST 0x80
#define WHO_AM_I_KXCJ9 0x0a
#define KXTJ9_CTRL1_CONFIG (RES_12BIT | KXTJ9_G_2G | DRDYE)
/* Misc. driver defitions ***************************************************/
#define ACCEL_NUM_RETRIES 5
/****************************************************************************
* Private Types
****************************************************************************/
/* This structure describes the state of one KXTJ9 device */
struct kxtj9_dev_s
{
FAR struct i2c_master_s *i2c;
sem_t exclsem;
bool enable;
bool power_enabled;
uint8_t address;
uint8_t shift;
uint8_t ctrl_reg1;
uint8_t data_ctrl;
uint8_t int_ctrl;
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* I2C helpers */
static int kxtj9_reg_read(FAR struct kxtj9_dev_s *priv, uint8_t regaddr,
FAR uint8_t *regval, unsigned int len);
static int kxtj9_reg_write(FAR struct kxtj9_dev_s *priv,
uint8_t regaddr, uint8_t regval);
/* KXTJ9 helpers */
static int kxtj9_configure(FAR struct kxtj9_dev_s *priv, uint8_t odr);
static int kxtj9_enable(FAR struct kxtj9_dev_s *priv, bool on);
static int kxtj9_read_sensor_data(FAR struct kxtj9_dev_s *priv,
FAR struct kxtj9_sensor_data *sensor_data);
static void kxtj9_soft_reset(FAR struct kxtj9_dev_s *priv);
static void kxtj9_set_mode_standby(FAR struct kxtj9_dev_s *priv);
/* Character driver methods */
static int kxtj9_open(FAR struct file *filep);
static int kxtj9_close(FAR struct file *filep);
static ssize_t kxtj9_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t kxtj9_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int kxtj9_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_fops =
{
kxtj9_open,
kxtj9_close,
kxtj9_read,
kxtj9_write,
NULL,
kxtj9_ioctl,
#ifndef CONFIG_DISABLE_POLL
NULL,
#endif
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
NULL,
#endif
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: kxtj9_reg_read
*
* Description:
* Read from multiple KXTJ9 registers.
*
****************************************************************************/
static int kxtj9_reg_read(FAR struct kxtj9_dev_s *priv, uint8_t regaddr,
FAR uint8_t *regval, unsigned int len)
{
struct i2c_msg_s msg[2];
uint8_t buf[1];
int retries = ACCEL_NUM_RETRIES;
int ret;
do
{
/* Format two messages: The first is a write containing the register
* address
*/
buf[0] = regaddr;
msg[0].frequency = CONFIG_KXTJ9_I2C_BUS_SPEED,
msg[0].addr = priv->address;
msg[0].flags = 0;
msg[0].buffer = buf;
msg[0].length = 1;
/* The second is a read with a restart containing the register data */
msg[1].frequency = CONFIG_KXTJ9_I2C_BUS_SPEED,
msg[1].addr = priv->address;
msg[1].flags = I2C_M_READ;
msg[1].buffer = regval;
msg[1].length = len;
/* Then perform the transfer. */
ret = I2C_TRANSFER(priv->i2c, msg, 2);
}
while (ret < 0 && retries-- > 0);
return ret;
}
/****************************************************************************
* Name: kxtj9_reg_write
*
* Description:
* Write a value to a single KXTJ9 register
*
****************************************************************************/
static int kxtj9_reg_write(FAR struct kxtj9_dev_s *priv, uint8_t regaddr,
uint8_t regval)
{
struct i2c_msg_s msg;
uint8_t buf[2];
int ret;
int retries = ACCEL_NUM_RETRIES;
do
{
/* Setup for the transfer */
buf[0] = regaddr;
buf[1] = regval;
msg.frequency = CONFIG_KXTJ9_I2C_BUS_SPEED,
msg.addr = priv->address;
msg.flags = 0;
msg.buffer = buf;
msg.length = 2;
/* Then perform the transfer. */
ret = I2C_TRANSFER(priv->i2c, &msg, 1);
}
while (ret < 0 && retries-- > 0);
return ret;
}
/****************************************************************************
* Name: kxtj9_soft_reset
*
* Description:
* Configure the KXTJ9 device. Handler the SNIOC_CONFIGURE IOCTL command.
*
****************************************************************************/
static void kxtj9_soft_reset(FAR struct kxtj9_dev_s *priv)
{
uint8_t wbuf[1];
/* Set accel into standby and known state by disabling PC1 */
wbuf[0] = KXTJ9_CTRL1_CONFIG;
kxtj9_reg_write(priv, CTRL_REG1, wbuf[0]);
/* Send the reset command */
kxtj9_reg_read(priv, CTRL_REG2, &wbuf[0], 1);
wbuf[0] |= KXTJ9_SRST;
kxtj9_reg_write(priv, CTRL_REG2, wbuf[0]);
/* Delay 10ms for the accel parts to re-initialize */
nxsig_usleep(10000);
}
/****************************************************************************
* Name: kxtj9_set_mode_standby
*
* Description:
* Configure the KXTJ9 device. Handler the SNIOC_CONFIGURE IOCTL command.
*
****************************************************************************/
static void kxtj9_set_mode_standby(FAR struct kxtj9_dev_s *priv)
{
uint8_t wbuf[1];
/* Set Accel into standby and known state by disabling PC1 */
wbuf[0] = KXTJ9_CTRL1_CONFIG;
kxtj9_reg_write(priv, CTRL_REG1, wbuf[0]);
/* Clear interrupts */
wbuf[0] = 0;
kxtj9_reg_write(priv, INT_CTRL1, wbuf[0]);
}
/****************************************************************************
* Name: kxtj9_configure
*
* Description:
* Configure the KXTJ9 device. Handler the SNIOC_CONFIGURE IOCTL command.
*
****************************************************************************/
static int kxtj9_configure(FAR struct kxtj9_dev_s *priv, uint8_t odr)
{
uint8_t wbuf[0];
int ret;
do
{
ret = nxsem_wait(&priv->exclsem);
}
while (ret == -EINTR);
kxtj9_soft_reset(priv);
kxtj9_set_mode_standby(priv);
/* Read WHO_AM_I register, should return 0x0a */
kxtj9_reg_read(priv, WHO_AM_I, &wbuf[0], 1);
if (wbuf[0] != WHO_AM_I_KXCJ9)
{
snerr("ERROR: Not KXCJ9 chipset, WHO_AM_I register is 0x%2x.\n",
wbuf[0]);
}
/* Ensure that PC1 is cleared before updating control registers */
kxtj9_reg_write(priv, CTRL_REG1, 0);
/* 12Bit Res and -2G~+2G range */
priv->ctrl_reg1 = KXTJ9_CTRL1_CONFIG;
kxtj9_reg_write(priv, CTRL_REG1, priv->ctrl_reg1);
priv->data_ctrl = odr;
kxtj9_reg_write(priv, DATA_CTRL, priv->data_ctrl);
/* In irq mode, populate INT_CTRL */
priv->int_ctrl = KXTJ9_IEN | KXTJ9_IEA | KXTJ9_IEL;
kxtj9_reg_write(priv, INT_CTRL1, priv->int_ctrl);
nxsem_post(&priv->exclsem);
return 0;
}
/****************************************************************************
* Name: kxtj9_enable
*
* Description:
* Enable or disable the KXTJ9 device. Handler the SNIOC_ENABLE and
* SNIOC_DISABLE IOCTL commands.
*
****************************************************************************/
static int kxtj9_enable(FAR struct kxtj9_dev_s *priv, bool on)
{
uint8_t wbuf[1];
int ret;
do
{
ret = nxsem_wait(&priv->exclsem);
}
while (ret == -EINTR);
if (!on && priv->power_enabled)
{
priv->ctrl_reg1 &= PC1_OFF;
kxtj9_reg_write(priv, CTRL_REG1, priv->ctrl_reg1);
priv->power_enabled = false;
sninfo("KXTJ9 in disabled mode\n");
}
else if (on && !priv->power_enabled)
{
/* Turn on outputs */
priv->ctrl_reg1 |= PC1_ON;
kxtj9_reg_write(priv, CTRL_REG1, priv->ctrl_reg1);
/* Clear initial interrupt if in irq mode */
kxtj9_reg_read(priv, INT_REL, wbuf, 1);
priv->power_enabled = true;
sninfo("KXTJ9 in operating mode\n");
}
nxsem_post(&priv->exclsem);
return OK;
}
/****************************************************************************
* Name: kxtj9_read_sensor_data
*
* Description:
* Read sensor data. This supports the standard driver read() method.
*
****************************************************************************/
static int kxtj9_read_sensor_data(FAR struct kxtj9_dev_s *priv,
FAR struct kxtj9_sensor_data *sensor_data)
{
int16_t acc_data[3];
uint8_t data;
int ret;
do
{
ret = nxsem_wait(&priv->exclsem);
}
while (ret == -EINTR);
kxtj9_reg_read(priv, XOUT_L, (uint8_t *)acc_data, 6);
/* 12 bit resolution, get rid of the lowest 4 bits */
sensor_data->x = acc_data[0] >> 4;
sensor_data->y = acc_data[1] >> 4;
sensor_data->z = acc_data[2] >> 4;
/* Read INT_REL to clear interrupt status */
kxtj9_reg_read(priv, INT_REL, &data, 1);
nxsem_post(&priv->exclsem);
/* Feed sensor data to entropy pool */
add_sensor_randomness((acc_data[0] << 16) ^ (acc_data[1] << 8) ^
acc_data[2]);
return OK;
}
/****************************************************************************
* Name: kxtj9_open
*
* Description:
* This method is called when the device is opened.
*
****************************************************************************/
static int kxtj9_open(FAR struct file *filep)
{
return OK;
}
/****************************************************************************
* Name: kxtj9_close
*
* Description:
* This method is called when the device is closed.
*
****************************************************************************/
static int kxtj9_close(FAR struct file *filep)
{
return OK;
}
/****************************************************************************
* Name: kxtj9_read
*
* Description:
* The standard read method.
*
****************************************************************************/
static ssize_t kxtj9_read(FAR struct file *filep, FAR char *buffer,
size_t buflen)
{
FAR struct inode *inode;
FAR struct kxtj9_dev_s *priv;
size_t nsamples;
size_t i;
int ret;
/* How many samples will fit in the buffer? */
nsamples = buflen / sizeof(struct kxtj9_sensor_data);
/* If the provided buffer is not large enough to return a single sample,
* then return an error.
*/
if (nsamples < 1)
{
snerr("ERROR: Bufer too small %lu < %u\n",
buflen, sizeof(struct kxtj9_sensor_data));
return (ssize_t)-EINVAL;
}
DEBUGASSERT(filep != NULL && filep->f_inode != NULL && buffer != NULL);
inode = filep->f_inode;
priv = (FAR struct kxtj9_dev_s *)inode->i_private;
DEBUGASSERT(priv != NULL && priv->i2c != NULL);
/* Return all of the samples that will fit in the user-provided buffer */
for (i = 0; i < nsamples; i++)
{
/* Get the next sample data */
ret = kxtj9_read_sensor_data(priv, (FAR struct kxtj9_sensor_data *)buffer);
if (ret < 0)
{
snerr("ERROR: kxtj9_read_sensor_data failed: %d\n", ret);
return (ssize_t)ret;
}
/* Set up for the next sample */
buffer += sizeof(struct kxtj9_sensor_data);
}
return (ssize_t)(nsamples * sizeof(struct kxtj9_sensor_data));
}
/****************************************************************************
* Name: kxtj9_write
*
* Description:
* A dummy write method.
*
****************************************************************************/
static ssize_t kxtj9_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen)
{
return -ENOSYS;
}
/****************************************************************************
* Name: kxtj9_ioctl
*
* Description:
* The standard ioctl method.
*
****************************************************************************/
static int kxtj9_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode;
FAR struct kxtj9_dev_s *priv;
int ret;
/* Sanity check */
DEBUGASSERT(filep != NULL && filep->f_inode != NULL);
inode = filep->f_inode;
priv = (FAR struct kxtj9_dev_s *)inode->i_private;
DEBUGASSERT(priv != NULL && priv->i2c != NULL);
/* Handle ioctl commands */
switch (cmd)
{
/* Start converting. Arg: None. */
case SNIOC_ENABLE:
ret = kxtj9_enable(priv, true);
break;
/* Stop converting. Arg: None. */
case SNIOC_DISABLE:
ret = kxtj9_enable(priv, false);
break;
/* Configure the KXTJ9. Arg: enum kxtj9_odr_e value. */
case SNIOC_CONFIGURE:
{
DEBUGASSERT(arg <= UINT8_MAX);
ret = kxtj9_configure(priv, (uint8_t)arg);
sninfo("SNIOC_CONFIGURE: ODR=%u ret=%d\n",
(unsigned int)arg, ret);
}
break;
/* Unrecognized commands */
default:
snerr("ERROR: Unrecognized cmd: %d arg: %lu\n", cmd, arg);
ret = -ENOTTY;
break;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: kxtj9_register
*
* Description:
* Register the KXTJ9 accelerometer device as 'devpath'.
*
* Input Parameters:
* devpath - The full path to the driver to register, e.g., "/dev/accel0".
* i2c - An I2C driver instance.
* addr - The I2C address of the KXTJ9 accelerometer, gyroscope or
* magnetometer.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int kxtj9_register(FAR const char *devpath, FAR struct i2c_master_s *i2c,
uint8_t address)
{
FAR struct kxtj9_dev_s *priv;
int ret;
/* Sanity check */
DEBUGASSERT(devpath != NULL && i2c != NULL);
/* Initialize the device's structure */
priv = (FAR struct kxtj9_dev_s *)kmm_zalloc(sizeof(struct kxtj9_dev_s));
if (priv == NULL)
{
snerr("ERROR: Failed to allocate driver instance\n");
return -ENOMEM;
}
priv->i2c = i2c;
priv->address = address;
nxsem_init(&priv->exclsem, 0, 1);
/* Register the character driver */
ret = register_driver(devpath, &g_fops, 0666, priv);
if (ret < 0)
{
snerr("ERROR: Failed to register driver: %d\n", ret);
kmm_free(priv);
return ret;
}
return OK;
}
#endif /* CONFIG_I2C && CONFIG_SENSORS_KXTJ9 */