sensor: fxas21002: Add gyroscope driver

Adds sensor driver support for the NXP FXAS21002 3-axis gyroscope.
Includes statically configurable range and output data rate, as well as
the sensor data ready trigger.

Datasheet:
http://www.nxp.com/assets/documents/data/en/data-sheets/FXAS21002.pdf

Jira: ZEP-1392
Change-Id: I84587c4d5e76863245e9d045c6abb10b21b2615a
Signed-off-by: Maureen Helm <maureen.helm@nxp.com>
This commit is contained in:
Maureen Helm 2017-03-28 07:36:49 -05:00 committed by Anas Nashif
commit f38ea1636a
7 changed files with 751 additions and 0 deletions

View file

@ -56,6 +56,8 @@ source "drivers/sensor/bmi160/Kconfig"
source "drivers/sensor/dht/Kconfig"
source "drivers/sensor/fxas21002/Kconfig"
source "drivers/sensor/fxos8700/Kconfig"
source "drivers/sensor/hdc1008/Kconfig"

View file

@ -8,6 +8,7 @@ obj-$(CONFIG_BME280) += bme280/
obj-$(CONFIG_BMG160) += bmg160/
obj-$(CONFIG_BMI160) += bmi160/
obj-$(CONFIG_DHT) += dht/
obj-$(CONFIG_FXAS21002) += fxas21002/
obj-$(CONFIG_FXOS8700) += fxos8700/
obj-$(CONFIG_HDC1008) += hdc1008/
obj-$(CONFIG_HMC5883L) += hmc5883l/

View file

@ -0,0 +1,116 @@
# Kconfig - FXAS21002 3-axis gyroscope
#
# Copyright (c) 2017, NXP
#
# SPDX-License-Identifier: Apache-2.0
#
menuconfig FXAS21002
bool "FXAS21002 gyroscope driver"
depends on SENSOR && I2C
default n
help
Enable driver for the FXAS21002 gyroscope
if FXAS21002
config FXAS21002_NAME
string "Device name"
default "FXAS21002"
config FXAS21002_I2C_NAME
string "I2C device name"
default I2C_0_NAME
config FXAS21002_I2C_ADDRESS
hex "I2C address"
range 0x20 0x21
default 0x20
help
The I2C slave address can be configured by the SA0 input pin. This
option should usually be set by the board defconfig.
config FXAS21002_WHOAMI
hex "WHOAMI value"
range 0x00 0xff
default 0xd7
help
The datasheet defines the value of the WHOAMI register, but some
pre-production devices can have a different value. It is unlikely you
should need to change this configuration option from the default.
config FXAS21002_RANGE
int "Full scale range"
range 0 3
default 0
help
Selects the full scale range
0: +/-2000 dps (62.5 mdps/LSB)
1: +/-1000 dps (31.25 mdps/LSB)
2: +/-500 dps (15.625 mdps/LSB)
3: +/-250 dps (7.8125 mdps/LSB)
config FXAS21002_DR
int "Output data rate"
range 0 7
default 3
help
Selects the output data rate
0: 800 Hz
1: 400 Hz
2: 200 Hz
3: 100 Hz
4: 50 Hz
5: 25 Hz
6: 12.5 Hz
7: 12.5 Hz
choice
prompt "Trigger mode"
default FXAS21002_TRIGGER_NONE
config FXAS21002_TRIGGER_NONE
bool "No trigger"
config FXAS21002_TRIGGER_GLOBAL_THREAD
bool "Use global thread"
select FXAS21002_TRIGGER
config FXAS21002_TRIGGER_OWN_THREAD
bool "Use own thread"
select FXAS21002_TRIGGER
endchoice
config FXAS21002_TRIGGER
bool
default n
if FXAS21002_TRIGGER
config FXAS21002_GPIO_NAME
string "GPIO device name"
config FXAS21002_GPIO_PIN
int "GPIO pin"
config FXAS21002_DRDY_INT1
bool "Data ready interrupt to INT1 pin"
default n
help
Say Y to route data ready interrupt to INT1 pin. Say N to route to
INT2 pin.
config FXAS21002_THREAD_PRIORITY
int "Own thread priority"
depends on FXAS21002_TRIGGER_OWN_THREAD
default 10
config FXAS21002_THREAD_STACK_SIZE
int "Own thread stack size"
depends on FXAS21002_TRIGGER_OWN_THREAD
default 1024
endif # FXAS21002_TRIGGER
endif # FXAS21002

View file

@ -0,0 +1,9 @@
# Makefile - FXAS21002 3-axis gyroscope
#
# Copyright (c) 2017, NXP
#
# SPDX-License-Identifier: Apache-2.0
#
obj-$(CONFIG_FXAS21002) += fxas21002.o
obj-$(CONFIG_FXAS21002_TRIGGER) += fxas21002_trigger.o

View file

@ -0,0 +1,301 @@
/*
* Copyright (c) 2017, NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <fxas21002.h>
#include <misc/util.h>
#include <misc/__assert.h>
/* Sample period in microseconds, indexed by output data rate encoding (DR) */
static const uint32_t sample_period[] = {
1250, 2500, 5000, 10000, 20000, 40000, 80000, 80000
};
static int fxas21002_sample_fetch(struct device *dev, enum sensor_channel chan)
{
const struct fxas21002_config *config = dev->config->config_info;
struct fxas21002_data *data = dev->driver_data;
uint8_t buffer[FXAS21002_MAX_NUM_BYTES];
int16_t *raw;
int ret = 0;
int i;
if (chan != SENSOR_CHAN_ALL) {
SYS_LOG_ERR("Unsupported sensor channel");
return -ENOTSUP;
}
k_sem_take(&data->sem, K_FOREVER);
/* Read all the channels in one I2C transaction. */
if (i2c_burst_read(data->i2c, config->i2c_address,
FXAS21002_REG_OUTXMSB, buffer, sizeof(buffer))) {
SYS_LOG_ERR("Could not fetch sample");
ret = -EIO;
goto exit;
}
/* Parse the buffer into raw channel data (16-bit integers). To save
* RAM, store the data in raw format and wait to convert to the
* normalized sensor_value type until later.
*/
raw = &data->raw[0];
for (i = 0; i < sizeof(buffer); i += 2) {
*raw++ = (buffer[i] << 8) | (buffer[i+1]);
}
exit:
k_sem_give(&data->sem);
return ret;
}
static void fxas21002_convert(struct sensor_value *val, int16_t raw,
enum fxas21002_range range)
{
int32_t micro_rad;
/* Convert units to micro radians per second.*/
micro_rad = (raw * 62500) >> range;
val->val1 = micro_rad / 1000000;
val->val2 = micro_rad % 1000000;
}
static int fxas21002_channel_get(struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
const struct fxas21002_config *config = dev->config->config_info;
struct fxas21002_data *data = dev->driver_data;
int start_channel;
int num_channels;
int16_t *raw;
int ret;
int i;
k_sem_take(&data->sem, K_FOREVER);
/* Start with an error return code by default, then clear it if we find
* a supported sensor channel.
*/
ret = -ENOTSUP;
/* Convert raw gyroscope data to the normalized sensor_value type. */
switch (chan) {
case SENSOR_CHAN_GYRO_X:
start_channel = FXAS21002_CHANNEL_GYRO_X;
num_channels = 1;
break;
case SENSOR_CHAN_GYRO_Y:
start_channel = FXAS21002_CHANNEL_GYRO_Y;
num_channels = 1;
break;
case SENSOR_CHAN_GYRO_Z:
start_channel = FXAS21002_CHANNEL_GYRO_Z;
num_channels = 1;
break;
case SENSOR_CHAN_GYRO_XYZ:
start_channel = FXAS21002_CHANNEL_GYRO_X;
num_channels = 3;
break;
default:
start_channel = 0;
num_channels = 0;
break;
}
raw = &data->raw[start_channel];
for (i = 0; i < num_channels; i++) {
fxas21002_convert(val++, *raw++, config->range);
}
if (num_channels > 0) {
ret = 0;
}
if (ret != 0) {
SYS_LOG_ERR("Unsupported sensor channel");
}
k_sem_give(&data->sem);
return ret;
}
int fxas21002_get_power(struct device *dev, enum fxas21002_power *power)
{
const struct fxas21002_config *config = dev->config->config_info;
struct fxas21002_data *data = dev->driver_data;
uint8_t val = *power;
if (i2c_reg_read_byte(data->i2c, config->i2c_address,
FXAS21002_REG_CTRLREG1,
&val)) {
SYS_LOG_ERR("Could not get power setting");
return -EIO;
}
val &= FXAS21002_CTRLREG1_POWER_MASK;
*power = val;
return 0;
}
int fxas21002_set_power(struct device *dev, enum fxas21002_power power)
{
const struct fxas21002_config *config = dev->config->config_info;
struct fxas21002_data *data = dev->driver_data;
return i2c_reg_update_byte(data->i2c, config->i2c_address,
FXAS21002_REG_CTRLREG1,
FXAS21002_CTRLREG1_POWER_MASK,
power);
}
uint32_t fxas21002_get_transition_time(enum fxas21002_power start,
enum fxas21002_power end,
uint8_t dr)
{
uint32_t transition_time;
/* If not transitioning to active mode, then don't need to wait */
if (end != FXAS21002_POWER_ACTIVE) {
return 0;
}
/* Otherwise, the transition time depends on which state we're
* transitioning from. These times are defined by the datasheet.
*/
transition_time = sample_period[dr];
if (start == FXAS21002_POWER_READY) {
transition_time += 5000;
} else {
transition_time += 60000;
}
return transition_time;
}
static int fxas21002_init(struct device *dev)
{
const struct fxas21002_config *config = dev->config->config_info;
struct fxas21002_data *data = dev->driver_data;
uint32_t transition_time;
uint8_t whoami;
uint8_t ctrlreg1;
/* Get the I2C device */
data->i2c = device_get_binding(config->i2c_name);
if (data->i2c == NULL) {
SYS_LOG_ERR("Could not find I2C device");
return -EINVAL;
}
/* Read the WHOAMI register to make sure we are talking to FXAS21002
* and not some other type of device that happens to have the same I2C
* address.
*/
if (i2c_reg_read_byte(data->i2c, config->i2c_address,
FXAS21002_REG_WHOAMI, &whoami)) {
SYS_LOG_ERR("Could not get WHOAMI value");
return -EIO;
}
if (whoami != config->whoami) {
SYS_LOG_ERR("WHOAMI value received 0x%x, expected 0x%x",
whoami, config->whoami);
return -EIO;
}
/* Reset the sensor. Upon issuing a software reset command over the I2C
* interface, the sensor immediately resets and does not send any
* acknowledgment (ACK) of the written byte to the master. Therefore,
* do not check the return code of the I2C transaction.
*/
i2c_reg_write_byte(data->i2c, config->i2c_address,
FXAS21002_REG_CTRLREG1, FXAS21002_CTRLREG1_RST_MASK);
/* Wait for the reset sequence to complete */
do {
if (i2c_reg_read_byte(data->i2c, config->i2c_address,
FXAS21002_REG_CTRLREG1, &ctrlreg1)) {
SYS_LOG_ERR("Could not get ctrlreg1 value");
return -EIO;
}
} while (ctrlreg1 & FXAS21002_CTRLREG1_RST_MASK);
/* Set the full-scale range */
if (i2c_reg_update_byte(data->i2c, config->i2c_address,
FXAS21002_REG_CTRLREG0,
FXAS21002_CTRLREG0_FS_MASK,
config->range)) {
SYS_LOG_ERR("Could not set range");
return -EIO;
}
/* Set the output data rate */
if (i2c_reg_update_byte(data->i2c, config->i2c_address,
FXAS21002_REG_CTRLREG1,
FXAS21002_CTRLREG1_DR_MASK,
config->dr << FXAS21002_CTRLREG1_DR_SHIFT)) {
SYS_LOG_ERR("Could not set output data rate");
return -EIO;
}
#if CONFIG_FXAS21002_TRIGGER
if (fxas21002_trigger_init(dev)) {
SYS_LOG_ERR("Could not initialize interrupts");
return -EIO;
}
#endif
/* Set active */
if (fxas21002_set_power(dev, FXAS21002_POWER_ACTIVE)) {
SYS_LOG_ERR("Could not set active");
return -EIO;
}
/* Wait the transition time from standby to active mode */
transition_time = fxas21002_get_transition_time(FXAS21002_POWER_STANDBY,
FXAS21002_POWER_ACTIVE,
config->dr);
k_busy_wait(transition_time);
k_sem_init(&data->sem, 0, UINT_MAX);
k_sem_give(&data->sem);
SYS_LOG_DBG("Init complete");
return 0;
}
static const struct sensor_driver_api fxas21002_driver_api = {
.sample_fetch = fxas21002_sample_fetch,
.channel_get = fxas21002_channel_get,
#if CONFIG_FXAS21002_TRIGGER
.trigger_set = fxas21002_trigger_set,
#endif
};
static const struct fxas21002_config fxas21002_config = {
.i2c_name = CONFIG_FXAS21002_I2C_NAME,
.i2c_address = CONFIG_FXAS21002_I2C_ADDRESS,
.whoami = CONFIG_FXAS21002_WHOAMI,
.range = CONFIG_FXAS21002_RANGE,
.dr = CONFIG_FXAS21002_DR,
#ifdef CONFIG_FXAS21002_TRIGGER
.gpio_name = CONFIG_FXAS21002_GPIO_NAME,
.gpio_pin = CONFIG_FXAS21002_GPIO_PIN,
#endif
};
static struct fxas21002_data fxas21002_data;
DEVICE_AND_API_INIT(fxas21002, CONFIG_FXAS21002_NAME, fxas21002_init,
&fxas21002_data, &fxas21002_config,
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY,
&fxas21002_driver_api);

View file

@ -0,0 +1,107 @@
/*
* Copyright (c) 2017, NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <sensor.h>
#include <i2c.h>
#include <gpio.h>
#define SYS_LOG_DOMAIN "FXAS21002"
#define SYS_LOG_LEVEL CONFIG_SYS_LOG_SENSOR_LEVEL
#include <logging/sys_log.h>
#define FXAS21002_REG_STATUS 0x00
#define FXAS21002_REG_OUTXMSB 0x01
#define FXAS21002_REG_INT_SOURCE 0x0b
#define FXAS21002_REG_WHOAMI 0x0c
#define FXAS21002_REG_CTRLREG0 0x0d
#define FXAS21002_REG_CTRLREG1 0x13
#define FXAS21002_REG_CTRLREG2 0x14
#define FXAS21002_REG_CTRLREG3 0x15
#define FXAS21002_INT_SOURCE_DRDY_MASK (1 << 0)
#define FXAS21002_CTRLREG0_FS_MASK (3 << 0)
#define FXAS21002_CTRLREG1_DR_SHIFT 2
#define FXAS21002_CTRLREG1_POWER_MASK (3 << 0)
#define FXAS21002_CTRLREG1_DR_MASK (7 << FXAS21002_CTRLREG1_DR_SHIFT)
#define FXAS21002_CTRLREG1_RST_MASK (1 << 6)
#define FXAS21002_CTRLREG2_CFG_EN_MASK (1 << 2)
#define FXAS21002_CTRLREG2_CFG_DRDY_MASK (1 << 3)
#define FXAS21002_MAX_NUM_CHANNELS 3
#define FXAS21002_BYTES_PER_CHANNEL 2
#define FXAS21002_MAX_NUM_BYTES (FXAS21002_BYTES_PER_CHANNEL * \
FXAS21002_MAX_NUM_CHANNELS)
enum fxas21002_power {
FXAS21002_POWER_STANDBY = 0,
FXAS21002_POWER_READY = 1,
FXAS21002_POWER_ACTIVE = 3,
};
enum fxas21002_range {
FXAS21002_RANGE_2000DPS = 0,
FXAS21002_RANGE_1000DPS,
FXAS21002_RANGE_500DPS,
FXAS21002_RANGE_250DPS,
};
enum fxas21002_channel {
FXAS21002_CHANNEL_GYRO_X = 0,
FXAS21002_CHANNEL_GYRO_Y,
FXAS21002_CHANNEL_GYRO_Z,
};
struct fxas21002_config {
char *i2c_name;
#ifdef CONFIG_FXAS21002_TRIGGER
char *gpio_name;
uint8_t gpio_pin;
#endif
uint8_t i2c_address;
uint8_t whoami;
enum fxas21002_range range;
uint8_t dr;
};
struct fxas21002_data {
struct device *i2c;
struct k_sem sem;
#ifdef CONFIG_FXAS21002_TRIGGER
struct device *gpio;
uint8_t gpio_pin;
struct gpio_callback gpio_cb;
sensor_trigger_handler_t drdy_handler;
#endif
#ifdef CONFIG_FXAS21002_TRIGGER_OWN_THREAD
char __stack thread_stack[CONFIG_FXAS21002_THREAD_STACK_SIZE];
struct k_sem trig_sem;
#endif
#ifdef CONFIG_FXAS21002_TRIGGER_GLOBAL_THREAD
struct k_work work;
struct device *dev;
#endif
int16_t raw[FXAS21002_MAX_NUM_CHANNELS];
};
int fxas21002_get_power(struct device *dev, enum fxas21002_power *power);
int fxas21002_set_power(struct device *dev, enum fxas21002_power power);
uint32_t fxas21002_get_transition_time(enum fxas21002_power start,
enum fxas21002_power end,
uint8_t dr);
#if CONFIG_FXAS21002_TRIGGER
int fxas21002_trigger_init(struct device *dev);
int fxas21002_trigger_set(struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
#endif

View file

@ -0,0 +1,215 @@
/*
* Copyright (c) 2017, NXP
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <fxas21002.h>
static void fxas21002_gpio_callback(struct device *dev,
struct gpio_callback *cb,
uint32_t pin_mask)
{
struct fxas21002_data *data =
CONTAINER_OF(cb, struct fxas21002_data, gpio_cb);
if ((pin_mask & BIT(data->gpio_pin)) == 0) {
return;
}
gpio_pin_disable_callback(dev, data->gpio_pin);
#if defined(CONFIG_FXAS21002_TRIGGER_OWN_THREAD)
k_sem_give(&data->trig_sem);
#elif defined(CONFIG_FXAS21002_TRIGGER_GLOBAL_THREAD)
k_work_submit(&data->work);
#endif
}
static int fxas21002_handle_drdy_int(struct device *dev)
{
struct fxas21002_data *data = dev->driver_data;
struct sensor_trigger drdy_trig = {
.type = SENSOR_TRIG_DATA_READY,
.chan = SENSOR_CHAN_ALL,
};
if (data->drdy_handler) {
data->drdy_handler(dev, &drdy_trig);
}
return 0;
}
static void fxas21002_handle_int(void *arg)
{
struct device *dev = (struct device *)arg;
const struct fxas21002_config *config = dev->config->config_info;
struct fxas21002_data *data = dev->driver_data;
uint8_t int_source;
k_sem_take(&data->sem, K_FOREVER);
if (i2c_reg_read_byte(data->i2c, config->i2c_address,
FXAS21002_REG_INT_SOURCE,
&int_source)) {
SYS_LOG_ERR("Could not read interrupt source");
int_source = 0;
}
k_sem_give(&data->sem);
if (int_source & FXAS21002_INT_SOURCE_DRDY_MASK) {
fxas21002_handle_drdy_int(dev);
}
gpio_pin_enable_callback(data->gpio, config->gpio_pin);
}
#ifdef CONFIG_FXAS21002_TRIGGER_OWN_THREAD
static void fxas21002_thread_main(void *arg1, void *unused1, void *unused2)
{
struct device *dev = (struct device *)arg1;
struct fxas21002_data *data = dev->driver_data;
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
while (true) {
k_sem_take(&data->trig_sem, K_FOREVER);
fxas21002_handle_int(dev);
}
}
#endif
#ifdef CONFIG_FXAS21002_TRIGGER_GLOBAL_THREAD
static void fxas21002_work_handler(struct k_work *work)
{
struct fxas21002_data *data =
CONTAINER_OF(work, struct fxas21002_data, work);
fxas21002_handle_int(data->dev);
}
#endif
int fxas21002_trigger_set(struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
const struct fxas21002_config *config = dev->config->config_info;
struct fxas21002_data *data = dev->driver_data;
enum fxas21002_power power = FXAS21002_POWER_STANDBY;
uint32_t transition_time;
uint8_t mask;
int ret = 0;
k_sem_take(&data->sem, K_FOREVER);
switch (trig->type) {
case SENSOR_TRIG_DATA_READY:
mask = FXAS21002_CTRLREG2_CFG_EN_MASK;
data->drdy_handler = handler;
break;
default:
SYS_LOG_ERR("Unsupported sensor trigger");
ret = -ENOTSUP;
goto exit;
}
/* The sensor must be in standby or ready mode when writing the
* configuration registers, therefore get the current power mode so we
* can restore it later.
*/
if (fxas21002_get_power(dev, &power)) {
SYS_LOG_ERR("Could not get power mode");
ret = -EIO;
goto exit;
}
/* Put the sensor in ready mode */
if (fxas21002_set_power(dev, FXAS21002_POWER_READY)) {
SYS_LOG_ERR("Could not set ready mode");
ret = -EIO;
goto exit;
}
/* Configure the sensor interrupt */
if (i2c_reg_update_byte(data->i2c, config->i2c_address,
FXAS21002_REG_CTRLREG2,
mask,
handler ? mask : 0)) {
SYS_LOG_ERR("Could not configure interrupt");
ret = -EIO;
goto exit;
}
/* Restore the previous power mode */
if (fxas21002_set_power(dev, power)) {
SYS_LOG_ERR("Could not restore power mode");
ret = -EIO;
goto exit;
}
/* Wait the transition time from ready mode */
transition_time = fxas21002_get_transition_time(FXAS21002_POWER_READY,
power,
config->dr);
k_busy_wait(transition_time);
exit:
k_sem_give(&data->sem);
return ret;
}
int fxas21002_trigger_init(struct device *dev)
{
const struct fxas21002_config *config = dev->config->config_info;
struct fxas21002_data *data = dev->driver_data;
uint8_t ctrl_reg2;
#if defined(CONFIG_FXAS21002_TRIGGER_OWN_THREAD)
k_sem_init(&data->trig_sem, 0, UINT_MAX);
k_thread_spawn(data->thread_stack, CONFIG_FXAS21002_THREAD_STACK_SIZE,
fxas21002_thread_main, dev, 0, NULL,
K_PRIO_COOP(CONFIG_FXAS21002_THREAD_PRIORITY), 0, 0);
#elif defined(CONFIG_FXAS21002_TRIGGER_GLOBAL_THREAD)
data->work.handler = fxas21002_work_handler;
data->dev = dev;
#endif
/* Route the interrupts to INT1/INT2 pins */
ctrl_reg2 = 0;
#if CONFIG_FXAS21002_DRDY_INT1
ctrl_reg2 |= FXAS21002_CTRLREG2_CFG_DRDY_MASK;
#endif
if (i2c_reg_write_byte(data->i2c, config->i2c_address,
FXAS21002_REG_CTRLREG2, ctrl_reg2)) {
SYS_LOG_ERR("Could not configure interrupt pin routing");
return -EIO;
}
/* Get the GPIO device */
data->gpio = device_get_binding(config->gpio_name);
if (data->gpio == NULL) {
SYS_LOG_ERR("Could not find GPIO device");
return -EINVAL;
}
data->gpio_pin = config->gpio_pin;
gpio_pin_configure(data->gpio, config->gpio_pin,
GPIO_DIR_IN | GPIO_INT | GPIO_INT_EDGE |
GPIO_INT_ACTIVE_LOW | GPIO_INT_DEBOUNCE);
gpio_init_callback(&data->gpio_cb, fxas21002_gpio_callback,
BIT(config->gpio_pin));
gpio_add_callback(data->gpio, &data->gpio_cb);
gpio_pin_enable_callback(data->gpio, config->gpio_pin);
return 0;
}