diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index a89b11367bb..47dfaff1895 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -178,6 +178,13 @@ config LIS331DL_I2C_FREQUENCY range 1 100000 depends on LIS331DL +config SENSORS_LSM330SPI + bool "STMicro LSM330 SPI support" + default n + select SPI + ---help--- + Enable driver support for the STMicro LSM330 on SPI. + config SENSORS_LSM9DS1 bool "STMicro LSM9DS1 support" default n @@ -303,7 +310,7 @@ config MPL115A_REGDEBUG Enables register level debug features for the MPL115A config SENSORS_ADXL345 - bool "AnalogDevices ADXL345 Driver" + bool "Analog Devices ADXL345 Driver" default n ---help--- Enables support for the ADXL345 driver @@ -345,6 +352,13 @@ config ADXL345_REGDEBUG endif # SENSORS_ADXL345 +config SENSORS_ADXL372 + bool "Analog Devices ADXL372 Sensor support" + default n + select SPI + ---help--- + Enable driver support for the Analog Devices ADXL372 Sensor. + config SENSORS_MAX44009 bool "Maxim MAX44009 ALS sensor" default n diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index 18e55bfecb6..e8d6e15734f 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -146,6 +146,10 @@ ifeq ($(CONFIG_ADXL345_SPI),y) CSRCS += adxl345_spi.c endif +ifeq ($(CONFIG_SENSORS_ADXL372),y) + CSRCS += adxl372.c +endif + ifeq ($(CONFIG_LIS3DSH),y) CSRCS += lis3dsh.c endif @@ -170,6 +174,10 @@ ifeq ($(CONFIG_SENSORS_LIS3MDL),y) CSRCS += lis3mdl.c endif +ifeq ($(CONFIG_SENSORS_LSM330SPI),y) + CSRCS += lsm330_spi.c +endif + ifeq ($(CONFIG_SENSORS_L3GD20),y) CSRCS += l3gd20.c endif diff --git a/drivers/sensors/README.txt b/drivers/sensors/README.txt index e0df3d62971..fcc50c71488 100644 --- a/drivers/sensors/README.txt +++ b/drivers/sensors/README.txt @@ -1,4 +1,4 @@ -ADXL345 +ADXL345 (Alan Carvalho de Assis) ======= The ADXL345 accelerometer can operate in I2C or SPI mode. To operate in I2C @@ -7,10 +7,458 @@ mode just connect the CS pin to Vddi/o. In order to operate in SPI mode CS need to use connected to microcontroller, it cannot leave unconnected. -In SPI mode it works with clock polarity (CPOL) = 1 and clock phase (CPHA) = 1. +In SPI mode it works with clock polarity (CPOL) = 1 and clock phase (CPHA) += 1. -MPL115A +ADXL372 (Bob Feretich) ======= -This driver has support only for MPL115A1 (SPI), but support to MPL115A2 (I2C) can -be added easily. +The ADXL372 is a 200g tri-axis accelerometer that is capable of detecting +and recording shock impact impact events. Recording trigger +characteristics are programed into the sensor via multiple threshold and +duration registers. The ADXL372 is a SPI only device that can transfer +data at 10 MHz. The data transfer performance of this part permits the +sensor to be sampled "on demand" rather than periodically sampled by a +worker task. + +See the description of the "Common Sensor Register Interface" below for more +details. It also implements the "Sensor Cluster Driver Interface". + +LSM330_SPI (Bob Feretich) +========== + +The LSM330 consists of a multi-range tri-axis accelerometer and a +multi-range tri-axis gyroscope. The tri-axis accelerometer features two +state machines that can be firmware programmed for event detection. The +tri-axis gyroscope features threshold and duration registers for event +detection. + +This driver supports the LSM330 in SPI mode. In this mode, the LSM330 +that can transfer data at 10 MHz. The data transfer performance of +this part permits the sensor to be sampled "on demand" rather than +periodically sampled by a worker task. See the description of the "Common +Sensor Register Interface" below for more details. It also implements the +"Sensor Cluster Driver Interface". + +MPL115A (Alan Carvalho de Assis) +======= + +This driver has support only for MPL115A1 (SPI), but support to MPL115A2 +(I2C) can be added easily. + +Common Sensor Register Interface (Bob Feretich) +================================ + +Background and problem statement: + +The capabilities and performance of modern sensors have grown tremendously. +Most sensors are now capable of some degree of autonomous behavior and +several permit the user to load firmware into them and perform as +nanocontrollers. Other sensors have very sophisticated built-in digital +filters that can be programmed with hundreds of parameters. + +Currently most sensor drivers in the Nuttx drivers/sensors +directory implement file_ops open(), close(), and read() functions. +The open() function initializes the sensor and places it in a mode where +it can transfer live data in a default configuration. The close() function +places the sensor in a low power shutdown mode. The read() function +returns the most recent data sample from the sensor's most used data +output registers. The write() function is rarely implemented and when it +is there is no consistency in its use. The lseek() and poll() functions +seem to be completely ignored. This results in the sensors being operated +in only their most primitive modes using a fixed "default configuration". + +To work around this problem sensor drivers have implemented ioctl() +functions to perform configuration, program the sensor, and manage +autonomous activity. Ioctls provide a method where user programs can +tunnel through a high level driver to access and control device specific +features. The problem with using ioctls is that before the ioctl interface +can be used, the sensor driver must be opened; and the open() function +causes the driver to start performing these primitive actions, so before +ioctls can manage the drivers as desired, ioctls must first be used to +undo the generic actions caused by the open() function. Another major +issue is that there is no consistency from sensor to sensor on ioctl +definitions, not even for the most common sensor actions like writing a +sensor control register or reading a sensor status register. + +Purpose: + +The purpose of the "Common Sensor Register Interface" is to implement a +consistent and more useful definition of file_ops interface and to make the +file_ops open() function more flexible in establishing the initial +operational state of the sensor. Compatibility for user applications that +implement the current open(), close(), read() interface will be +maintained; and the much greater capabilities of modern sensors will +become accessible through this interface. + +Scope: + +Applicable to I2C and SPI attached sensors, and some serial port attached +sensors. + +The file_ops interface definition: + +open(): This function performs the below actions... + + 1) Reads the sensors ID register. If the sensor responds with an + unexpected value, then... + + a) The driver's write() function is disabled. + b) The open function initializes the driver instance, so + that read() and lseek() operations may be performed to enable + problem diagnoses, but the sensor hardware is not initialized. + (No write operations are performed to the sensor.) + c) The errno global variable is set to positive ENODEV + ("No such device"). + d) The open() function returns successfully with a file_handle. + Note that the calling routine should clear errno before + calling open(). (The file_ops rules prevent drivers from + setting errno to zero.) + + 2) The other file_ops functions are enabled. + 3) The driver's "current reg address" state variable is set to the + sensor's first sensor data output register. (This will make + calls to read() return live sensor data and maintain compatibility + with existing user programs.) + 4) If the driver supports a default worker task and an interrupt + handler is specified by in the sensor configuration structure, then + the default worker task is bound to the default worker task. + 5) The sensor configuration structure (that was provided to the driver + registration function) is examined to determine whether a custom + sensor configuration is specified. (The custom configuration is + basically an array of (device_reg_address, value) pairs that are + written to the sensor via "single register write" operations. + If a custom sensor configuration was specified, then the that + configuration is written to the sensor, otherwise the "default + sensor configuration" is written to the sensor. + (A side effect of writing this data may result in interrupts + occurring and data being transferred to/from the worker task.) + 6) The open() function returns successfully with a file_handle. + +close(): This function stops sensor activity and places it in a low + power mode. The file_ops interface functions are disabled for this + instance of the sensor driver. (Except for open()) + +read(): The action of this function is dependent on whether a "default + worker task" is running and the value of the driver's "current reg + address" state variable. + + If a "default worker task" is running, + + AND the driver's "current reg address" is equal to the value of + the first sensor data output register, + AND the number of bytes to be read is less than or equal to the + number of bytes in a "default worker task" sample, + + Then data is copied from the "default worker task's" sample memory to + the caller's provided buffer. + + Otherwise, this function transfers data from sensor registers to the + data buffer provided by the caller. The first byte read is from the + sensor register address specified by the sensor's "current reg + address". The addresses of subsequent bytes to be read are context + sensitive. If more than bus transfer is needed to complete the read, + then a "multi-byte" (sometimes called "burst mode") data transfer + will be used to fill the buffer. + See the sensor's datasheet to determine the auto-increment + behavior of a "multi-byte" data transfers. + + Note: That most sensors collect only a few bytes of data per sample. + Small data transfers occurring over a high speed bus (like SPI and some + high speed i2c and serial interfaces) are much more efficient when + collected directly from the sensor hardware than by using a worker task + as an intermediary. + +write(): This function transfers data from the data buffer provided by + the caller to sensor registers. The first byte written is to the + sensor register address specified by the sensor's "current reg + address". The addresses of subsequent bytes to be read are context + sensitive. If more than bus transfer is needed to complete the write, + then a "multi-byte" (sometimes called "burst mode") data + transfer will be used to transfer data from the buffer. + + See the sensor's datasheet to determine the auto-increment + behavior of a "multi-byte" data transfers. + + Note: If write() function was disabled, then no writes will be performed + and the function will return 0 (characters transferred) and errno + is set to -EROFS ("read-only file system"). + +lseek(): This function sets the value of the sensor's "current reg address" + (seek_address). The open() function initializes the "current reg address" + to the first sensor data output register, so unless the user needs + to change the sensor configuration, lseek() does not need to be + called. Neither read() nor write() change the sensor's "current reg + address". + + The definition of lseek is... + + off_t lseek(int fd, off_t offset, int whence); + + For whence == SEEK_SET, the sensor's "current reg address" will be set + to offset. + + For whence == SEEK_CUR, offset will be added to the sensor's "current + reg address". + + For whence == SEEK_END, offset is ignored and the sensor's "current + reg address" is set to the first sensor data output register. + + lseek() will return an error if the resulting "current reg address" + is invalid for the sensor. + +ioctl(): Ioctls() may still be used and this interface make no attempt to + regulate them. But, it is expected that far fewer ioctls will be needed. + +The above interface can be used to fully configure a sensor to the needs +of an application, including the ability to load firmware into sensor +state machines + +Sensor Cluster Driver Interface:(Bob Feretich) +=============================== + +Background and problem statement: + +Most microcontrollers can support SPI bus transfers at 8 MHz or greater. +Most SPI attached sensors can support a 10 MHz SPI bus. Most tri-axis +accelerometers, tri-axis gyroscopes, or tri-axis magnetometers use only 6 +bytes per sample. Many sensors use less than 6 bytes per sample. On an 8 +MHz SPI bus it takes about 8 microseconds to transfer a 6 byte sample. +(This time includes a command byte, 6 data bytes, and chip select select +setup and hold.) So, for the below discussion keep in mind that the sensor +sample collection work we want to perform should ideally take 8 microseconds +per sample. + +The drivers in the drivers/sensors directory support only the user space +file_ops interface (accessing drivers through the POSIX open/read/close +functions using a file descriptor). Also these drivers typically start +their own worker task to perform sensor data collection, even when their +sensors only transfer a few bytes of data per sample and those transfers +are being made over a high performance bus. + +Using the current implementation... + + 1) A sensor "data ready" or timer interrupt occurs. + 2) Context is saved and and the driver's interrupt handler is scheduled + to run. + 3) The Nuttx scheduler dispatches the driver's interrupt handler task. + 4) The driver's interrupt handler task posts to a semaphore that the + driver's worker task is waiting on. + 5) Nuttx restores the context for the driver's worker task and starts it + running. + 6) The driver's worker task starts the i/o to collect the sample.) (This is + where the 8 microseconds of real work gets performed.) And waits on a + SPI data transfer complete semaphore. + 7) The Nuttx saves the context of the driver's worker task, and the + scheduler dispatches some other task to run while we are waiting. + Note that this is a good thing. This task is probably performing some + other real work. We want this to happen during the data transfer. + 8) The completion of the data transfer causes an interrupt. Nuttx saves the + current context and restores the driver's worker task's context. + 9) The driver's worker task goes to sleep waiting on the semaphore for the + next sensor "data ready" or timer interrupt. +10) The Nuttx saves the context of the driver's worker task, and the + scheduler dispatches some other task to run while we are waiting. + +Independently with the above... + +a) The sensor application program performs a file_ops read() to collect a + sample. +b) The Nuttx high level driver receives control, performs a thin layer of + housekeeping and calls the sensor driver's read function. +c) The sensor driver's read function copies the most recent sample from the + worker task's data area to the application's buffer and returns. +d) The Nuttx high level driver receives control, performs a thin layer of + housekeeping and returns. +e) The application processes the sample. + +Using a 216 MHz STM32F7 with no other activity occurring, we have timed the +above the elapsed time for the above to be on average 45 microseconds. + +Most sensor applications process data from multiple sensors. (An 9-DoF IMU +is typically represented as three sensors (accelerometer, gyroscope, and +magnetometer). In this case there are three copies of 1-10 occurring in +parallel. + +In applications where live data is being used, the context switch +thrashing and cache pollution of this approach cripples system +performance. In applications where sensor FIFO data is being used and +therefore a large amount of data is collected per iteration, the non "zero +copy" nature of the data collection becomes a performance issue. + +Purpose: + +The "Sensor Cluster Driver Interface" provides a standard mechanism for +an application to collect data from multiple sensor drivers in a much more +efficient manner. It significantly reduces the number of running tasks and +the context thrashing and cache pollution caused by them. It also permits +"zero copy" collection of sensor data. + +The Sensor Cluster Driver Interface uses a single "worker task" to be shared +by an arbitrary number of drivers. This shared worker task is a kernel +task that is registered like a driver, supports a driver interface to +application programs, and collects data from multiple sensors (a cluster of +sensors), we refer to it a "Sensor Cluster Driver". + +Its goal is to change the sequence of events detailed above to... + + 1) A sensor "data ready" or timer interrupt occurs. + 2) Context is saved and and the cluster driver's interrupt handler is + scheduled to run. + 3) The Nuttx scheduler dispatches the cluster driver's interrupt handler + task. + 4) The cluster driver's interrupt handler task posts to a semaphore that + the cluster driver's worker task is waiting on. + 5) Nuttx restores the context for the driver's worker task and starts it + running. + 6) The cluster driver's worker task starts the i/o to collect the sample. + There are two choices here. Programed I/O (PIO) or DMA. If PIO is + fastest for a small sample size, but it will lock up the processor for + the full duration of the transfer; it can only transfer from one + sensor at a time; and the worker task should manually yield control + occasionally to permit other tasks to run. DMA has higher start and + completion overhead, but it is much faster for long transfers, can + perform simultaneous transfers from sensors on different buses, and + automatically releases the processor while the transfer is occurring. + For this reason our drivers allows the worker task to choose between + PIO (driver_read()) and DMA (driver_exchange()), a common extension to + the sensor_cluster_operations_s structure. So either way after one or + more transfers we yield control and move to the next step. Note that + the data is being transferred directly into the buffer provided by the + application program; so no copy needs to be performed. + 7) The Nuttx saves the context of the cluster driver's worker task, and the + scheduler dispatches some other task to run while we are waiting. + Again note that this is a good thing. This task is probably performing + some other real work. We want this to happen during the data transfer. + 8) The completion of the last of the previous data transfers causes an + interrupt. Nuttx saves the current context and restores the cluster + driver's worker task's context. If there is more sensor data to + collect, then goto Step 6. Otherwise it posts to a semaphore that + will wake the application. + 9) The driver's worker task goes to sleep waiting on the semaphore for the + next sensor "data ready" or timer interrupt. +10) The Nuttx saves the context of the driver's worker task, and the + scheduler dispatches some other task to run while we are waiting. + +Independently with the above... + +a) The sensor application program performs a file_ops read() to collect a + sample. +b) The Nuttx high level driver receives control, performs a thin layer of + housekeeping and calls the sensor driver's read function. +c) The sensor driver's read function copies the most recent sample from the + worker task's data area to the application's buffer and returns. +d) The Nuttx high level driver receives control, performs a thin layer of + housekeeping and returns. +e) The application processes the sample. + +So when collecting data from three sensors, this mechanism saved... + + * the handling of 2 sensor "data ready" or timer interrupts (Steps 1 - 4). + * 2 occurrences of waking and scheduling of a worker task (Step 5). + * 2 context switches to other tasks (Step 9 & 10) + * if the three sensors were on separate buses, then 2 occurrences of + +Steps 6 - 8 could have also been saved. + + * An extra copy operation of the collected sensor data. + * The cache pollution caused by 2 competing worker tasks. + +Definitions: + +Leaf Driver - a kernel driver that implements the "Sensor Cluster Driver + Interface" so that it can be called by Cluster drivers. +Cluster Driver - a kernel driver that uses the "Sensor Cluster Driver + Interface" to call leaf drivers. +Entry-Point Vector - an array of function addresses to which a leaf driver + will permit calls by a Cluster Driver. +Leaf Driver Instance Handle - a pointer to an opaque Leaf Driver structure + that identifies an instance of the leaf driver. Leaf Drivers store this + handle in its configuration structure during registration. + +Sensor Cluster Interface description: + + * The definition of an entry-point vector. This is similar to the + entry-point vector that is provided to the file-ops high level driver. + This entry-point vector must include the sensor_cluster_operations_s + structure as its first member. + * The the definition of an driver entry-point vector member in the leaf + driver's configuration structure. The leaf driver registration function + must store the address of its entry-point vector in this field. + * The the definition of an instance handle member in the leaf drivers + configuration structure. The leaf driver registration function must store + a handle (opaque pointer) to the instance of the leaf driver being + registered in this field. Note that this should be the same handle that + the leaf driver supplies to Nuttx to register itself. The cluster driver + will include this handle as a parameter in calls made to the leaf driver. + +struct sensor_cluster_operations_s +{ + CODE int (*driver_open)(FAR void *instance_handle, int32_t arg); + CODE int (*driver_close)(FAR void *instance_handle, int32_t arg); + CODE ssize_t (*driver_read)(FAR void *instance_handle, FAR char *buffer, + size_t buflen); + CODE ssize_t (*driver_write)(FAR void *instance_handle, + FAR const char *buffer, size_t buflen); + CODE off_t (*driver_seek)(FAR void *instance_handle, off_t offset, + int whence); + CODE int (*driver_ioctl)(FAR void *instance_handle, int cmd, + unsigned long arg); + CODE int (*driver_suspend)(FAR void *instance_handle, int32_t arg); + CODE int (*driver_resume)(FAR void *instance_handle, int32_t arg); +}; + +Note that the sensor_cluster_operations_s strongly resembles the Nuttx fs.h +file_operations structures. This permits the current file_operations +functions to become thin wrappers around these functions. + +driver_open(): Same as the fs.h open() except that arg can be specify + permitting more flexibility in sensor configuration and initial operation. + when arg = 0 the function of driver_open() must be identical to open(). + +driver_close(): Same as the fs.h close() except that arg can be specify + permitting more flexibility in selecting a sensor low power state. + when arg = 0 the function of driver_close() must be identical to close(). + +driver_read(): Same as the fs.h read(). + +driver_write(): Same as the fs.h write(). Optional. Set to NULL if not + supported. + +driver_seek(): Same as the fs.h seek(). Optional. Set to NULL if not + supported. + +driver_ioctl(): Same as the fs.h ioctl(). Optional. Set to NULL if not + supported. + +driver_suspend() and driver_resume(): Optional. Set to NULL if not + supported. It is common for sensor applications to conserve power and + send their microcontroller into a low power sleep state. It seems + appropriate to reserve these spots for future use. These driver entry + points exist in Linux and Windows. Since microcontrollers and sensors + get more capable every year, there should soon be a requirement for + these entry points. Discussion on how to standardize their use and + implementation should + be taken up independently from this driver document. + +Note that all drivers are encouraged to extend their entry-point vectors +beyond this common segment. For example it may be beneficial for the +worker task to select between programed i/o and DMA data transfer +routines. Unregulated extensions to the Entry-Point Vector should be +encouraged to maximize the benefits of a sensor's features. + +Operation: + +Board logic (configs directory) will register the cluster driver. The +cluster driver will register the leaf drivers that it will call. +This means that the cluster driver has access to the leaf driver's +configuration structures and can pass the Leaf Driver Instance Handle to +the leaf driver as a parameter in calls made via the Entry-Point Vector. + +Either board logic or an application program may open() the cluster +driver. The cluster driver open() calls the open() function of the leaf +drivers. The cluster driver open() or read() function can launch the +shared worker task that collects the data. + +The cluster driver close() function calls the close functions of the leaf +drivers. diff --git a/drivers/sensors/adxl372.c b/drivers/sensors/adxl372.c new file mode 100644 index 00000000000..21b6581316f --- /dev/null +++ b/drivers/sensors/adxl372.c @@ -0,0 +1,905 @@ +/**************************************************************************** + * drivers/sensors/adxl372.c + * Character driver for the ST ADXL372 Tri-axis accelerometer and gyroscope. + * + * Copyright (C) 2017-2018 RAF Research LLC. All rights reserved. + * Author: Bob Feretich + * + * 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 + +#if defined(CONFIG_SPI) && defined(CONFIG_SENSORS_ADXL372) \ + && defined(CONFIG_SPI_EXCHANGE) + +#include +#include +#include +#include + +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define ADXL372_INITIAL_CR_SIZE 7 + +/**************************************************************************** + * Private structure definitions + ****************************************************************************/ + +struct sensor_data_s +{ + int16_t x_gyr; /* Measurement result for x axis */ + int16_t y_gyr; /* Measurement result for y axis */ + int16_t z_gyr; /* Measurement result for z axis */ +}; + +struct adxl372_dev_s +{ + FAR struct adxl372_dev_s *flink; /* Supports a singly linked list of + * drivers */ + FAR struct spi_dev_s *spi; /* Pointer to the SPI instance */ + FAR struct adxl372_config_s *config; /* Pointer to the configuration of the + * ADXL372 sensor */ + sem_t devicesem; /* Manages exclusive access to this + * device */ + sem_t datasem; /* Manages exclusive access to this + * structure */ + struct sensor_data_s data; /* The data as measured by the sensor */ + uint8_t seek_address; /* Current device address. */ + uint8_t readonly; /* 0 = writing to the device in enabled */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static uint8_t adxl372_read_register(FAR struct adxl372_dev_s *dev, + uint8_t reg_addr); +static void adxl372_read_registerblk(FAR struct adxl372_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt); +static void adxl372_write_register(FAR struct adxl372_dev_s *dev, + uint8_t reg_addr, + uint8_t reg_data); +static void adxl372_write_registerblk(FAR struct adxl372_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt); +static void adxl372_reset(FAR struct adxl372_dev_s *dev); + +static int adxl372_open(FAR struct file *filep); +static int adxl372_close(FAR struct file *filep); +static ssize_t adxl372_read(FAR struct file *, FAR char *, size_t); +static ssize_t adxl372_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static off_t adxl372_seek(FAR struct file *filep, off_t offset, + int whence); +static int adxl372_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); + +static int adxl372_dvr_open(FAR void *instance_handle, int32_t arg); +static int adxl372_dvr_close(FAR void *instance_handle, int32_t arg); +static ssize_t adxl372_dvr_read(FAR void *instance_handle, + FAR char *buffer, size_t buflen); +static ssize_t adxl372_dvr_write(FAR void *instance_handle, + FAR const char *buffer, size_t buflen); +static off_t adxl372_dvr_seek(FAR void *instance_handle, off_t offset, + int whence); +static int adxl372_dvr_ioctl(FAR void *instance_handle, int cmd, + unsigned long arg); +static void adxl372_dvr_exchange(FAR void *instance_handle, + FAR const void *txbuffer, + FAR void *rxbuffer, size_t nwords); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_adxl372_fops = +{ + adxl372_open, + adxl372_close, + adxl372_read, + adxl372_write, + adxl372_seek, + adxl372_ioctl +#ifndef CONFIG_DISABLE_POLL + , NULL +#endif +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , NULL +#endif +}; + +static const struct adxl372_dvr_entry_vector_s g_adxl372_dops = +{ + /* Standard sensor cluster driver entry-vector */ + + { + .driver_open = adxl372_dvr_open, + .driver_close = adxl372_dvr_close, + .driver_read = adxl372_dvr_read, + .driver_write = adxl372_dvr_write, + .driver_seek = adxl372_dvr_seek, + .driver_ioctl = adxl372_dvr_ioctl, + .driver_suspend = 0, + .driver_resume = 0, + }, + + /* adxl372 extensions follow */ + + .driver_spiexc = adxl372_dvr_exchange, +}; + +/**************************************************************************** + * Private data storage + ****************************************************************************/ + +/* Single linked list to store instances of drivers */ + +static struct adxl372_dev_s *g_adxl372_list = NULL; + +/* Default accelerometer initialization sequence */ + +/* Configure ADXL372 to read live data (not using FIFO). + * 1. Set to standby mode. The below can't be set while running. + * 2. Configure the FIFO to be bypassed. + * 3. Configure interrupts as disabled, because ADXL372 irpts are used. + * 4. Configure the Output Data Rate (ODR) as 1600 Hz. + * 5. Configure normal mode (vs low noise) and 800Hz bandwidth. + * 6. Set to operational mode; 370ms filter settle; LPF=enb; HPF=dis; + */ + +static struct adxl372_reg_pair_s g_initial_adxl372_cr_values[] = +{ + /* Set to standby mode */ + + { + .addr = ADXL372_POWER_CTL, + .value = 0 + }, + { + .addr = ADXL372_FIFO_CTL, + .value = ADXL372_FIFO_BYPASSED + }, + + /* Interrupts disabled. */ + + { + .addr = ADXL372_INT1_MAP, + .value = 0 + }, + { + .addr = ADXL372_TIMING, + .value = ADXL372_TIMING_ODR1600 + }, + { + .addr = ADXL372_MEASURE, + .value = ADXL372_MEAS_BW800 + }, + { + .addr = ADXL372_POWER_CTL, + .value = ADXL372_POWER_HPF_DISABLE | ADXL372_POWER_MODE_MEASURE + } +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: adxl372_read_register + ****************************************************************************/ + +static uint8_t adxl372_read_register(FAR struct adxl372_dev_s *dev, + uint8_t reg_addr) +{ + uint8_t reg_data; + + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + SPI_SETFREQUENCY(dev->spi, ADXL372_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, ADXL372_SPI_MODE); + + /* Set CS to low to select the ADXL372 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address from where we want to read. */ + + SPI_SEND(dev->spi, reg_addr | ADXL372_READ); + + /* Write an idle byte while receiving the requested data */ + + reg_data = (uint8_t) (SPI_SEND(dev->spi, 0xff)); + + /* Set CS to high to deselect the ADXL372 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); + + return reg_data; +} + +/****************************************************************************** + * Name: adxl372_read_registerblk + ******************************************************************************/ + + static void adxl372_read_registerblk(FAR struct adxl372_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt) +{ + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + SPI_SETFREQUENCY(dev->spi, ADXL372_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, ADXL372_SPI_MODE); + + /* Set CS to low to select the ADXL372 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address from where we want to start reading */ + + SPI_SEND(dev->spi, reg_addr | ADXL372_READ); + + /* Write idle bytes while receiving the requested data */ + + while ( 0 != xfercnt-- ) + { + *reg_data++ = (uint8_t)SPI_SEND(dev->spi, 0xff); + } + + /* Set CS to high to deselect the ADXL372 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); +} + +/**************************************************************************** + * Name: adxl372_write_register + ****************************************************************************/ + +static void adxl372_write_register(FAR struct adxl372_dev_s *dev, + uint8_t reg_addr, uint8_t reg_data) +{ + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + SPI_SETFREQUENCY(dev->spi, ADXL372_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, ADXL372_SPI_MODE); + + /* Set CS to low to select the ADXL372 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address to where we want to write */ + + SPI_SEND(dev->spi, reg_addr | ADXL372_WRITE); + + /* Transmit the content which should be written into the register */ + + SPI_SEND(dev->spi, reg_data); + + /* Set CS to high to deselect the ADXL372 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); +} + +/**************************************************************************** + * Name: adxl372_write_registerblk + ****************************************************************************/ + + static void adxl372_write_registerblk(FAR struct adxl372_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt) +{ + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + SPI_SETFREQUENCY(dev->spi, ADXL372_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, ADXL372_SPI_MODE); + + /* Set CS to low which selects the ADXL372 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address to where we want to start writing */ + + SPI_SEND(dev->spi, reg_addr | ADXL372_WRITE); + + /* Transmit the content which should be written in the register block */ + + while ( 0 != xfercnt-- ) + { + SPI_SEND(dev->spi, *reg_data++); + } + + /* Set CS to high to deselect the ADXL372 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); +} + +/**************************************************************************** + * Name: adxl372_reset + * + * Description: + * ADXL Accelerometer Reset + * 1. Make sure that a reset is not in progress. + * 2. Write ADXL372_RESET_VALUE (0x52) to ADXL372_RESET register. + * 3. Wait for the reset to finish. + * + ****************************************************************************/ + +static void adxl372_reset(FAR struct adxl372_dev_s *dev) +{ + uint wdcnt = 10; + + /* Wait for boot to finish (15 ms error timeout) */ + + up_mdelay(5); + while (wdcnt > 0 && (0 != adxl372_read_register(dev, ADXL372_RESET))) + { + up_mdelay(1); + } + + /* Reset ADXL372 Accelerometer. Write only. Begin a boot. */ + + adxl372_write_register(dev, ADXL372_RESET, ADXL372_RESET_VALUE); + + /* Wait for boot to finish (15 ms error timeout) */ + + up_mdelay(5); + wdcnt = 10; + while (wdcnt>0 && (0 != adxl372_read_register(dev, ADXL372_RESET))) + { + up_mdelay(1); + } +} + +/**************************************************************************** + * Name: adxl372_read_id + * + * Description: + * + * Read the ADXL372 Accelerometer's ID Registers. + * There are 4 ID Registers... + * + * Manufacturer should be ADXL372_DEVID_AD_VALUE (0xAD). + * Family should be ADXL372_DEVID_MST_VALUE (0x1D). + * Part ID should be ADXL372_PARTID_VALUE (0xFA, Octal 372) + * Revision is returned, but not expected to be checked. + * All of the above are returned as an uint32_t. Should be 0xAD1DFAxx. + * + ****************************************************************************/ + +static uint32_t adxl372_read_id(FAR struct adxl372_dev_s *dev) +{ + union + { + uint32_t adxl_devid32; + uint8_t adxl_devid[4]; + } un; + + un.adxl_devid[3] = adxl372_read_register(dev, ADXL372_DEVID_AD); + un.adxl_devid[2] = adxl372_read_register(dev, ADXL372_DEVID_MST); + un.adxl_devid[1] = adxl372_read_register(dev, ADXL372_PARTID); + un.adxl_devid[0] = adxl372_read_register(dev, ADXL372_REVID); + return un.adxl_devid32; +} + +/**************************************************************************** + * Name: adxl372_dvr_open + ****************************************************************************/ + +static int adxl372_dvr_open(FAR void *instance_handle, int32_t arg) +{ + FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance_handle; + FAR struct adxl372_reg_pair_s *initp; + uint32_t pnpid; + int sz; + int ret; + int i; + +#ifdef CONFIG_DEBUG_SENSORS_INFO + uint8_t reg_content; +#endif + + sninfo("adxl372_open: entered...\n"); + + DEBUGASSERT(priv != NULL); + UNUSED(arg); + + ret = nxsem_trywait(&priv->devicesem); + if (ret < 0) + { + sninfo("INFO: ADXL372 Accelerometer is already opened.\n"); + return -EBUSY; + } + + /* Read the ID registers */ + + pnpid = adxl372_read_id(priv); + priv->readonly = false; + + sninfo("ADXL372_ID = 0x%08x\n", pnpid); + + if ((pnpid & 0xffffff00) != (ADXL372_DEVID_AD_VALUE << 24 | + ADXL372_DEVID_MST_VALUE << 16 | + ADXL372_PARTID_VALUE << 8)) + { + snwarn("ERROR: Invalid ADXL372_ID = 0x%08x\n", pnpid); + + priv->readonly = true; + set_errno(ENODEV); + } + else /* ID matches */ + { + adxl372_reset(priv); /* Perform a sensor reset */ + + /* Choose the initialization sequence */ + + if (priv->config->initial_cr_values_size == 0 || + priv->config->initial_cr_values == NULL) + { + initp = g_initial_adxl372_cr_values; /* Default values */ + sz = ADXL372_INITIAL_CR_SIZE; + } + else + { + initp = priv->config->initial_cr_values; /* User supplied values */ + sz = priv->config->initial_cr_values_size; + } + + /* Apply the initialization sequence */ + + for (i = 0; i < sz; i++) + { + adxl372_write_register(priv, initp[i].addr, initp[i].value); + } + +#ifdef CONFIG_DEBUG_SENSORS_INFO + /* Read back the content of all control registers for debug purposes */ + + reg_content = adxl372_read_register(priv, ADXL372_FIFO_CTL); + sninfo("ADXL372_FIFO_CTL = 0x%02x\n", reg_content); + + reg_content = adxl372_read_register(priv, ADXL372_INT1_MAP); + sninfo("ADXL372_INT1_MAP = 0x%02x\n", reg_content); + + reg_content = adxl372_read_register(priv, ADXL372_TIMING); + sninfo("ADXL372_TIMING = 0x%02x\n", reg_content); + + reg_content = adxl372_read_register(priv, ADXL372_MEASURE); + sninfo("ADXL372_MEASURE = 0x%02x\n", reg_content); + + reg_content = adxl372_read_register(priv, ADXL372_POWER_CTL); + sninfo("ADXL372_POWER_CTL = 0x%02x\n", reg_content); +#endif + } + + priv->seek_address = (uint8_t) ADXL372_XDATA_H; + return OK; +} + +/**************************************************************************** + * Name: adxl372_dvr_close + ****************************************************************************/ + +static int adxl372_dvr_close(FAR void *instance_handle, int32_t arg) +{ + FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance_handle; + + DEBUGASSERT(priv != NULL); + UNUSED(arg); + + /* Perform a reset to place the sensor in standby mode.*/ + + adxl372_reset(priv); + + /* Release the sensor */ + + nxsem_post(&priv->devicesem); + return OK; +} + +/**************************************************************************** + * Name: adxl372_dvr_read + ****************************************************************************/ + +static ssize_t adxl372_dvr_read(FAR void *instance_handle, FAR char *buffer, + size_t buflen) +{ + FAR struct adxl372_dev_s *priv = ((FAR struct adxl372_dev_s *)instance_handle); + union + { + int16_t d16; + char d8[2]; + } un; + FAR char *p1; + FAR char *p2; + int i; + + DEBUGASSERT(priv != NULL); + + adxl372_read_registerblk(priv, priv->seek_address, (uint8_t *)buffer, + buflen); + + /* Permute accelerometer data out fields */ + + if (priv->seek_address == ADXL372_XDATA_H && buflen >= 6) + { + p1 = p2 = buffer; + for (i=0; i<3; i++) + { + un.d8[1] = *p1++; + un.d8[0] = *p1++; + un.d16 = un.d16 >> 4; + *p2++ = un.d8[0]; + *p2++ = un.d8[1]; + } + } + + return buflen; +} + +/**************************************************************************** + * Name: adxl372_dvr_write + ****************************************************************************/ + +static ssize_t adxl372_dvr_write(FAR void *instance_handle, + FAR const char *buffer, size_t buflen) +{ + FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance_handle; + + DEBUGASSERT(priv != NULL); + + if (priv->readonly) + { + set_errno(EROFS); + return -1; + } + + adxl372_write_registerblk(priv, priv->seek_address, (uint8_t *)buffer, + buflen); + + return buflen; +} + +/**************************************************************************** + * Name: adxl372_dvr_seek + ****************************************************************************/ + +static off_t adxl372_dvr_seek(FAR void *instance_handle, off_t offset, + int whence) +{ + off_t reg; + FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance_handle; + + DEBUGASSERT(priv != NULL); + + switch (whence) + { + case SEEK_CUR: /* Incremental seek */ + reg = priv->seek_address + offset; + if (0 > reg || reg > ADXL372_LAST) + { + set_errno(-EINVAL); + return -1; + } + + priv->seek_address = reg; + break; + + case SEEK_END: /* Seek to the 1st X-data register */ + priv->seek_address = ADXL372_XDATA_H; + break; + + case SEEK_SET: /* Seek to designated address */ + if (0 > offset || offset > ADXL372_LAST) + { + set_errno(-EINVAL); + return -1; + } + + priv->seek_address = offset; + break; + + default: /* invalid whence */ + set_errno(-EINVAL); + return -1; + } + + return priv->seek_address; +} + +/**************************************************************************** + * Name: adxl372_dvr_ioctl + ****************************************************************************/ + +static int adxl372_dvr_ioctl(FAR void *instance_handle, int cmd, + unsigned long arg) +{ + int ret = OK; + + switch (cmd) + { + /* Command was not recognized */ + + default: + snerr("ERROR: Unrecognized cmd: %d\n", cmd); + ret = -ENOTTY; + break; + } + + return ret; +} + +/**************************************************************************** + * Name: adxl372_dvr_exchange (with SPI DMA capability) + * + * Description: + * Exchange a block of data on SPI using DMA + * + * Input Parameters: + * instance_handle - Pointer to struct adxl372_dev_s. + * txbuffer - A pointer to the buffer of data to be sent + * rxbuffer - A pointer to a buffer in which to receive data + * nwords - the length of data to be exchanged in units of words. + * The wordsize is determined by the number of bits-per-word + * selected for the SPI interface. If nbits <= 8, the data is + * packed into uint8_t's; if nbits >8, the data is packed into + * uint16_t's + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void adxl372_dvr_exchange(FAR void *instance_handle, + FAR const void *txbuffer, + FAR void *rxbuffer, size_t nwords) +{ + FAR struct adxl372_dev_s *priv = (FAR struct adxl372_dev_s *)instance_handle; + FAR struct spi_dev_s *spi = priv->spi; + + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(spi, true); + + SPI_SETFREQUENCY(spi, ADXL372_SPI_FREQUENCY); + SPI_SETMODE(spi, ADXL372_SPI_MODE); + + /* Set CS to low which selects the ADXL372 */ + + SPI_SELECT(spi, priv->config->spi_devid, true); + + /* Perform an SPI exchange block operation. */ + + SPI_EXCHANGE(spi, txbuffer, rxbuffer, nwords); + + /* Set CS to high to deselect the ADXL372 */ + + SPI_SELECT(spi, priv->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(spi, false); + } + +/**************************************************************************** + * Name: adxl372_open + ****************************************************************************/ + + static int adxl372_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adxl372_dev_s *priv = inode->i_private; + int ret; + + ret = adxl372_dvr_open((FAR void *)priv, 0); + return ret; +} + +/**************************************************************************** + * Name: adxl372_close + ****************************************************************************/ + +static int adxl372_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adxl372_dev_s *priv = inode->i_private; + int ret; + + ret = adxl372_dvr_close((FAR void *)priv, 0); + return ret; +} + +/**************************************************************************** + * Name: adxl372_read + ****************************************************************************/ + +static ssize_t adxl372_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adxl372_dev_s *priv = inode->i_private; + + return adxl372_dvr_read(priv, buffer, buflen); +} + +/**************************************************************************** + * Name: adxl372_write + ****************************************************************************/ + +static ssize_t adxl372_write(FAR struct file *filep, FAR const char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adxl372_dev_s *priv = inode->i_private; + + return adxl372_dvr_write(priv, buffer, buflen); +} + +/**************************************************************************** + * Name: adxl372_seek + ****************************************************************************/ + +static off_t adxl372_seek(FAR struct file *filep, off_t offset, int whence) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adxl372_dev_s *priv = inode->i_private; + + return adxl372_dvr_seek(priv, offset, whence); +} + +/**************************************************************************** + * Name: adxl372_ioctl + ****************************************************************************/ + +static int adxl372_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct adxl372_dev_s *priv = inode->i_private; + + return adxl372_dvr_ioctl(priv, cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: adxl372_register + * + * Description: + * Register the ADXL372 character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/acl0" + * spi - An instance of the SPI interface to use to communicate with + * ADXL372 + * config - Configuration for the ADXL372 accelerometer driver. For + * details see description above. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int adxl372_register(FAR const char *devpath, + FAR struct spi_dev_s *spi, + FAR struct adxl372_config_s *config) +{ + FAR struct adxl372_dev_s *priv; + int ret; + + /* Sanity check */ + + DEBUGASSERT(spi != NULL); + DEBUGASSERT(config != NULL); + + /* Initialize the ADXL372 accelerometer device structure. */ + + priv = (FAR struct adxl372_dev_s *)kmm_malloc(sizeof(struct adxl372_dev_s)); + if (priv == NULL) + { + snerr("ERROR: Failed to allocate accelerometer instance\n"); + return -ENOMEM; + } + + priv->spi = spi; + priv->config = config; + config->leaf_handle = NULL; + config->sc_ops = NULL; + + /* Initialize sensor and sensor data access semaphore */ + + nxsem_init(&priv->devicesem, 0, 1); + nxsem_init(&priv->datasem, 0, 1); + + /* Register the character driver */ + + ret = register_driver(devpath, &g_adxl372_fops, 0666, priv); + if (ret < 0) + { + snerr("ERROR: Failed to register accelerometer driver: %d\n", ret); + + nxsem_destroy(&priv->datasem); + kmm_free(priv); + return ret; + } + + /* Since we support multiple ADXL372 devices, we will need to add this new + * instance to a list of device instances so that it can be found by the + * interrupt handler based on the received IRQ number. + */ + + priv->flink = g_adxl372_list; + g_adxl372_list = priv; + config->leaf_handle = (void *) priv; + config->sc_ops = &g_adxl372_dops; + + return OK; +} + +#endif /* CONFIG_SPI && CONFIG_SENSORS_ADXL372 && CONFIG_SPI_EXCHANGE */ diff --git a/drivers/sensors/lsm330_spi.c b/drivers/sensors/lsm330_spi.c new file mode 100644 index 00000000000..6a8d80490f8 --- /dev/null +++ b/drivers/sensors/lsm330_spi.c @@ -0,0 +1,1434 @@ +/**************************************************************************** + * drivers/sensors/lsm330.c + * Character driver for the ST LSM330 Tri-axis accelerometer and gyroscope. + * + * Copyright (C) 2017-2018 RAF Research LLC. All rights reserved. + * Author: Bob Feretich + * + * 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 + +#if defined(CONFIG_SPI) && defined(CONFIG_SENSORS_LSM330SPI) \ + && defined(CONFIG_SPI_EXCHANGE) + +#include +#include +#include +#include + +#include + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define LSM330_INITIAL_ACLCR_SIZE 7 +#define LSM330_INITIAL_GYROCR_SIZE 5 + +/**************************************************************************** + * Private structure definitions + ****************************************************************************/ + +struct sensor_data_s +{ + int16_t x_gyr; /* Measurement result for x axis */ + int16_t y_gyr; /* Measurement result for y axis */ + int16_t z_gyr; /* Measurement result for z axis */ +}; + +struct lsm330_dev_s +{ + FAR struct lsm330_dev_s *flink; /* Supports a singly linked list of + * drivers */ + FAR struct spi_dev_s *spi; /* Pointer to the SPI instance */ + FAR struct lsm330_config_s *config; /* Pointer to the configuration of the + * LSM330 sensor */ + sem_t devicesem; /* Manages exclusive access to this + * device */ + sem_t datasem; /* Manages exclusive access to this + * structure */ + struct sensor_data_s data; /* The data as measured by the sensor */ + uint8_t seek_address; /* Current device address. */ + uint8_t readonly; /* 0 = writing to the device in enabled */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static uint8_t lsm330_read_register(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr); +static void lsm330_read_acl_registerblk(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt); +static void lsm330_read_gyro_registerblk(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt); +static void lsm330_write_register(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + uint8_t reg_data); +static void lsm330_write_acl_registerblk(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt); +static void lsm330_write_gyro_registerblk(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt); +static void lsm330acl_reset(FAR struct lsm330_dev_s *dev); +static void lsm330gyro_reset(FAR struct lsm330_dev_s *dev); +static int lsm330acl_open(FAR struct file *filep); +static int lsm330gyro_open(FAR struct file *filep); +static int lsm330acl_close(FAR struct file *filep); +static int lsm330gyro_close(FAR struct file *filep); +static ssize_t lsm330acl_read(FAR struct file *, FAR char *, size_t); +static ssize_t lsm330gyro_read(FAR struct file *, FAR char *, size_t); +static ssize_t lsm330acl_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static ssize_t lsm330gyro_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen); +static off_t lsm330acl_seek(FAR struct file *filep, off_t offset, + int whence); +static off_t lsm330gyro_seek(FAR struct file *filep, off_t offset, + int whence); +static int lsm330_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); + +static int lsm330acl_dvr_open(FAR void *instance_handle, int32_t arg); +static int lsm330acl_dvr_close(FAR void *instance_handle, int32_t arg); +static int lsm330gyro_dvr_open(FAR void *instance_handle, int32_t arg); +static int lsm330gyro_dvr_close(FAR void *instance_handle, + int32_t arg); +static ssize_t lsm330acl_dvr_read(FAR void *instance_handle, + FAR char *buffer, size_t buflen); +static ssize_t lsm330gyro_dvr_read(FAR void *instance_handle, + FAR char *buffer, size_t buflen); +static ssize_t lsm330acl_dvr_write(FAR void *instance_handle, + FAR const char *buffer, size_t buflen); +static ssize_t lsm330gyro_dvr_write(FAR void *instance_handle, + FAR const char *buffer, size_t buflen); +static off_t lsm330acl_dvr_seek(FAR void *instance_handle, off_t offset, + int whence); +static off_t lsm330gyro_dvr_seek(FAR void *instance_handle, off_t offset, + int whence); +static int lsm330_dvr_ioctl(FAR void *instance_handle, int cmd, + unsigned long arg); +static void lsm330_dvr_exchange(FAR void *instance_handle, + FAR const void *txbuffer, + FAR void *rxbuffer, size_t nwords); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_lsm330a_fops = +{ + lsm330acl_open, + lsm330acl_close, + lsm330acl_read, + lsm330acl_write, + lsm330acl_seek, + lsm330_ioctl +#ifndef CONFIG_DISABLE_POLL + , NULL +#endif +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , NULL +#endif +}; + +static const struct file_operations g_lsm330g_fops = +{ + lsm330gyro_open, + lsm330gyro_close, + lsm330gyro_read, + lsm330gyro_write, + lsm330gyro_seek, + lsm330_ioctl +#ifndef CONFIG_DISABLE_POLL + , NULL +#endif +#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS + , NULL +#endif +}; + +static const struct lsm330spi_dvr_entry_vector_s g_lsm330acl_dops = +{ + { /* Standard sensor cluster driver entry-vector */ + .driver_open = lsm330acl_dvr_open, + .driver_close = lsm330acl_dvr_close, + .driver_read = lsm330acl_dvr_read, + .driver_write = lsm330acl_dvr_write, + .driver_seek = lsm330acl_dvr_seek, + .driver_ioctl = lsm330_dvr_ioctl, + .driver_suspend = 0, + .driver_resume = 0, + }, + + /* lsm330 extensions follow */ + + .driver_spiexc = lsm330_dvr_exchange, +}; + +static const struct lsm330spi_dvr_entry_vector_s g_lsm330gyro_dops = +{ + { /* Standard sensor cluster driver entry-vector */ + .driver_open = lsm330gyro_dvr_open, + .driver_close = lsm330gyro_dvr_close, + .driver_read = lsm330gyro_dvr_read, + .driver_write = lsm330gyro_dvr_write, + .driver_seek = lsm330gyro_dvr_seek, + .driver_ioctl = lsm330_dvr_ioctl, + .driver_suspend = 0, + .driver_resume = 0, + }, + + /* lsm330 extensions follow */ + + .driver_spiexc = lsm330_dvr_exchange, +}; + +/* Single linked list to store instances of drivers */ + +static struct lsm330_dev_s *g_lsm330a_list = NULL; +static struct lsm330_dev_s *g_lsm330g_list = NULL; + +/* Default accelerometer initialization sequence: + * + * Configure LSM330 to measure mode. + * + * 1. CR5: ODR=0, power-off + * 2. CR6: Bandwidth=800Hz. +/-16g range. 4-wire SPI. + * 3. CR7: Make sure auto-increment is turned on. + * 4. CR2: Zero state machine 1. + * 5. CR3: Zero state machine 2. + * 6. CR4: DataReady interrupt disabled. + * 7. CR5: Power up. 1600Hz sample rate. X,Y, & Z enabled. + */ + +static struct lsm330_reg_pair_s g_default_lsm330_aclcr_values[] = +{ + /* CR5 ODR[3:0] BDU ZEN YEN XEN */ + /* 0000=Off 0 0 0 0=all disabled */ + + { + .addr = LSM330_ACL_CTRL_REG5, + .value = 0x00 + }, + + /* CR6 BW[2:1] FSCALE[2:0] - - SIM */ + /* 00=800Hz 10 0=16g 0 0 0=4-wire */ + + { + .addr = LSM330_ACL_CTRL_REG6, + .value = 0x20 + }, + + /* CR7 BOOT FIFO_EN WTM_EN ADD_INC P1_MTY P1_WTM P1_OVR WTM_EN*/ + /* 0 0 0 1 0 0 0 0 */ + + { + .addr = LSM330_ACL_CTRL_REG7, + .value = 0x10 + }, + + /* CR2 HYST1 - SM1_PIN - - SM1_EN */ + /* 000 0 0 0 0 0 */ + + { + .addr = LSM330_ACL_CTRL_REG2, + .value = 0x00 + }, + + /* CR3 HYST2 - SM2_PIN - - SM2_EN */ + /* 000 0 0 0 0 0 */ + + { + .addr = LSM330_ACL_CTRL_REG3, + .value = 0x00 + }, + + /* CR4 DR_EN IEA IEL INT2_EN INT1_EN VFILT STRT */ + /* 1 1 0 0 0 0 0 */ + + { + .addr = LSM330_ACL_CTRL_REG4, + .value = 0xc0 + }, + + /* CR5 ODR[3:0] BDU ZEN YEN XEN */ + /* 1001=1600Hz 1 1 1 1=all enabled */ + + { + .addr = LSM330_ACL_CTRL_REG5, + .value = 0x9f + } +}; + +/* Default gyroscope initialization sequence + * + * Configure LSM330 Gyroscope to measure mode. + * + * 1. CR1: Power up. 760Hz sample rate. Bandwidth=100Hz. X,Y, & Z enabled. + * 2. CR2: Normal measurement mode. High pass filter OFF. + * 3. CR3: All interrupts disabled. + * 4. CR4: +/-500dps range. Block update. 4-wire SPI. + * 5. CR5: Select Low Pass Filter 1 outputs. LPF2 bypassed. + */ + +static struct lsm330_reg_pair_s g_default_lsm330_gyrocr_values[] = +{ + /* CR1 DR[1:0] BW[1:0] PD Zen Xen Yen */ + /* 1 1=760Hz 1 1=100Hz 1 1 1 1 */ + + { + .addr = LSM330_GYRO_CTRL_REG1, + .value = 0xFF + }, + + /* CR2 EXTRen LVLen HPM[1:0] HPCF[3:0] */ + /* 0 0 00=Normal xxxx Default */ + + { + .addr = LSM330_GYRO_CTRL_REG2, + .value = 0x00 + }, + + /* CR3 I1_Int1 I1_Boot H_Lactive PP_OD I2_DRDY I2_WTM I2_ORun I2_Empty */ + /* 0 0 0 0 0 0 0 0 */ + + { + .addr = LSM330_GYRO_CTRL_REG3, + .value = 0x00 + }, + + /* CR4 BDU BLE FS[1:0] 0 0 0 SIM */ + /* 1 0 01=500dps 0 0 0 0=4-wire */ + + { + .addr = LSM330_GYRO_CTRL_REG4, + .value = 0x90 + }, + + /* CR5 BOOT FIFO_EN - HPen INT1_Sel[1:0] Out_Sel[1:0] */ + /* 0 0 0 0 00=LPF1 00=LPF1 */ + + { + .addr = LSM330_GYRO_CTRL_REG5, + .value = 0x0a + } +}; + +/****************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lsm330_read_register + ****************************************************************************/ + +static uint8_t lsm330_read_register(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr) +{ + uint8_t reg_data; + + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + /* Setup SPI frequency and mode */ + + SPI_SETFREQUENCY(dev->spi, LSM330_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, LSM330_SPI_MODE); + + /* Set CS to low to select the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address from where we want to read. */ + + SPI_SEND(dev->spi, reg_addr | LSM330_READ); + + /* Write an idle byte while receiving the requested data */ + + reg_data = (uint8_t) (SPI_SEND(dev->spi, 0xff)); + + /* Set CS to high to deselect the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); + return reg_data; +} + +/****************************************************************************** + * Name: lsm330_read_acl_registerblk + ******************************************************************************/ + + static void lsm330_read_acl_registerblk(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt) +{ + + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + /* Setup SPI frequency and mode */ + + SPI_SETFREQUENCY(dev->spi, LSM330_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, LSM330_SPI_MODE); + + /* Set CS to low to select the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address from where we want to start reading */ + + SPI_SEND(dev->spi, reg_addr | LSM330_READ); + + /* Write idle bytes while receiving the requested data */ + + while ( 0 != xfercnt-- ) + { + *reg_data++ = (uint8_t)SPI_SEND(dev->spi, 0xff); + } + + /* Set CS to high to deselect the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); +} + +/****************************************************************************** + * Name: lsm330_read_gyro_registerblk + ******************************************************************************/ + + static void lsm330_read_gyro_registerblk(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt) +{ + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + /* Setup SPI frequency and mode */ + + SPI_SETFREQUENCY(dev->spi, LSM330_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, LSM330_SPI_MODE); + + /* Set CS to low to select the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address from where we want to start reading */ + + SPI_SEND(dev->spi, reg_addr | LSM330_READ | (xfercnt>1 ? LSM330_GYRO_AUTO : 0)); + + /* Write idle bytes while receiving the requested data */ + + while ( 0 != xfercnt-- ) + { + *reg_data++ = (uint8_t)SPI_SEND(dev->spi, 0xff); + } + + /* Set CS to high to deselect the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); +} + +/**************************************************************************** + * Name: lsm330_write_register + ****************************************************************************/ + +static void lsm330_write_register(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + uint8_t reg_data) +{ + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + /* Setup SPI frequency and mode */ + + SPI_SETFREQUENCY(dev->spi, LSM330_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, LSM330_SPI_MODE); + + /* Set CS to low to select the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address to where we want to write */ + + SPI_SEND(dev->spi, reg_addr | LSM330_WRITE); + + /* Transmit the content which should be written into the register */ + + SPI_SEND(dev->spi, reg_data); + + /* Set CS to high to deselect the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); +} + +/**************************************************************************** + * Name: lsm330_write_acl_registerblk + ****************************************************************************/ + + static void lsm330_write_acl_registerblk(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt) +{ + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + /* Setup SPI frequency and mode */ + + SPI_SETFREQUENCY(dev->spi, LSM330_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, LSM330_SPI_MODE); + + /* Set CS to low which selects the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address to where we want to start writing */ + + SPI_SEND(dev->spi, reg_addr | LSM330_WRITE); + + /* Transmit the content which should be written in the register block */ + + while ( 0 != xfercnt-- ) + { + SPI_SEND(dev->spi, *reg_data++); + } + + /* Set CS to high to deselect the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); +} + +/**************************************************************************** + * Name: lsm330_write_gyro_registerblk + ****************************************************************************/ + + static void lsm330_write_gyro_registerblk(FAR struct lsm330_dev_s *dev, + uint8_t reg_addr, + FAR uint8_t *reg_data, + uint8_t xfercnt) +{ + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(dev->spi, true); + + /* Setup SPI frequency and mode */ + + SPI_SETFREQUENCY(dev->spi, LSM330_SPI_FREQUENCY); + SPI_SETMODE(dev->spi, LSM330_SPI_MODE); + + /* Set CS to low which selects the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, true); + + /* Transmit the register address to where we want to start writing */ + + SPI_SEND(dev->spi, reg_addr | LSM330_WRITE | (xfercnt>1 ? LSM330_GYRO_AUTO : 0)); + + /* Transmit the content which should be written in the register block */ + + while ( 0 != xfercnt-- ) + { + SPI_SEND(dev->spi, *reg_data++); + } + + /* Set CS to high to deselect the LSM330 */ + + SPI_SELECT(dev->spi, dev->config->spi_devid, false); + + /* Unlock the SPI bus */ + + SPI_LOCK(dev->spi, false); +} + +/**************************************************************************** + * Name: lsm330acl_reset + ****************************************************************************/ + +static void lsm330acl_reset(FAR struct lsm330_dev_s *dev) +{ + /* Reset LSM330 Accelerometer. Write only. Begin a boot. + * Note that the LSM330 ACL does not set the BOOT bit for read, so we + * can't loop on it. + */ + + lsm330_write_register(dev, LSM330_ACL_CTRL_REG7, LSM330_ACR7_BOOT); + + /* Wait for boot to finish */ + + up_mdelay(20); + + /* Set auto-increment. + * + * CR7 BOOT FIFO_EN WTM_EN ADD_INC P1_MTY P1_WTM P1_OVR WTM_EN + * 0 0 0 1 0 0 0 0 + */ + + lsm330_write_register(dev, LSM330_ACL_CTRL_REG7, 0x10); +} + +/**************************************************************************** + * Name: lsm330gyro_reset + ****************************************************************************/ + +static void lsm330gyro_reset(FAR struct lsm330_dev_s *dev) +{ + /* Reset LSM330 Gyroscope. Write only. Begin a boot. + * Note that the LSM330 ACL does not set the BOOT bit for read, so we + * can't loop on it. + */ + + lsm330_write_register(dev, LSM330_GYRO_CTRL_REG5, LSM_GYRO_BOOT_MASK); + + /* Wait for boot to finish */ + + up_mdelay(20); +} + +/**************************************************************************** + * Name: lsm330acl_dvr_open + ****************************************************************************/ + +static int lsm330acl_dvr_open(FAR void *instance_handle, int32_t arg) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + FAR struct lsm330_reg_pair_s *initp; + uint8_t reg_content; + int ret; + int sz; + int i; + + sninfo("lsm330acl_open: entered...\n"); + + DEBUGASSERT(priv != NULL); + UNUSED(arg); + + ret = nxsem_trywait(&priv->devicesem); + if (ret < 0) + { + sninfo("INFO: LSM330 Accelerometer is already open.\n"); + + set_errno(-EBUSY); + return -1; + } + + /* Read the ID Register */ + + priv->readonly = false; + reg_content = lsm330_read_register(priv, LSM330_ACL_IDREG); + + sninfo("LSM330_ACL_IDREG = 0x%02x\n", reg_content); + + if (reg_content!=LSM330_ACL_IDREG_VALUE) + { + /* Made info log level to permit open being used as a device probe. */ + + snwarn("INFO: " + "Device ID (0x%02X) does not match expected LSM330 Acl ID (0x%02).\n", + reg_content, LSM330_ACL_IDREG_VALUE); + + set_errno(ENODEV); + priv->readonly = true; + } + else /* ID matches */ + { + lsm330acl_reset(priv); /* Perform a sensor reset */ + + /* Choose the initialization sequence */ + + if (priv->config->initial_cr_values_size == 0 || + priv->config->initial_cr_values == NULL) + { + initp = g_default_lsm330_aclcr_values; /* Default values */ + sz = LSM330_INITIAL_ACLCR_SIZE; + + sninfo("Using default CRs\n"); + } + else + { + initp = priv->config->initial_cr_values; /* User supplied values */ + sz = priv->config->initial_cr_values_size; + + sninfo("Using provided CRs\n"); + } + + /* Apply the initialization sequence */ + + for (i = 0; i < sz; i++) + { + lsm330_write_register(priv, initp[i].addr, initp[i].value); + } + +#ifdef CONFIG_DEBUG_SENSORS_INFO + /* Read back the content of all control registers for debug purposes */ + + reg_content = lsm330_read_register(priv, LSM330_ACL_CTRL_REG5); + sninfo("LSM330_ACL_CTRL_REG5 = 0x%02x\n", reg_content); + + reg_content = lsm330_read_register(priv, LSM330_ACL_CTRL_REG7); + sninfo("LSM330_ACL_CTRL_REG7 = 0x%02x\n", reg_content); + + reg_content = lsm330_read_register(priv, LSM330_ACL_CTRL_REG6); + sninfo("LSM330_ACL_CTRL_REG6 = 0x%02x\n", reg_content); + + reg_content = lsm330_read_register(priv, LSM330_ACL_CTRL_REG4); + sninfo("LSM330_ACL_CTRL_REG4 = 0x%02x\n", reg_content); +#endif + } + + priv->seek_address = (uint8_t) LSM330_ACL_OUT_X_L; + return OK; +} + +/**************************************************************************** + * Name: lsm330gyro_dvr_open + ****************************************************************************/ + +static int lsm330gyro_dvr_open(FAR void *instance_handle, int32_t arg) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + FAR struct lsm330_reg_pair_s *initp; + uint8_t reg_content; + int ret; + int sz; + int i; + + sninfo("lsm330gyro_open: entered...\n"); + + DEBUGASSERT(priv != NULL); + UNUSED(arg); + + ret = nxsem_trywait(&priv->devicesem); + if (ret < 0) + { + sninfo("INFO: LSM330 Gyroscope is already open.\n"); + set_errno(-EBUSY); + return -1; + } + + /* Read the ID Register */ + + priv->readonly = false; + reg_content = lsm330_read_register(priv, LSM330_GYRO_IDREG); + + sninfo("LSM330_GYRO_IDREG = 0x%02x\n", reg_content); + + if (reg_content!=LSM330_GYRO_IDREG_VALUE) + { + /* Made warning log level to permit open being used as a device probe. */ + + snwarn("INFO: " + "Device ID (0x%02X) does not match expected LSM330 Gyro ID (0x%02).\n", + reg_content, LSM330_GYRO_IDREG_VALUE); + + set_errno(ENODEV); + priv->readonly = true; + } + else /* ID matches */ + { + lsm330gyro_reset(priv); /* Perform a sensor reset */ + + /* Choose the initialization sequence */ + + if (priv->config->initial_cr_values_size == 0 || + priv->config->initial_cr_values == NULL) + { + initp = g_default_lsm330_gyrocr_values; /* Default values */ + sz = LSM330_INITIAL_GYROCR_SIZE; + } + else + { + initp = priv->config->initial_cr_values; /* User supplied values */ + sz = priv->config->initial_cr_values_size; + } + + /* Apply the initialization sequence */ + + for (i = 0; i < sz; i++) + { + lsm330_write_register(priv, initp[i].addr, initp[i].value); + } + +#ifdef CONFIG_DEBUG_SENSORS_INFO + /* Read back the content of all control registers for debug purposes */ + + reg_content = lsm330_read_register(priv, LSM330_GYRO_CTRL_REG1); + sninfo("LSM330_GYRO_CTRL_REG1 = 0x%02x\n", reg_content); + + reg_content = lsm330_read_register(priv, LSM330_GYRO_CTRL_REG2); + sninfo("LSM330_GYRO_CTRL_REG2 = 0x%02x\n", reg_content); + + reg_content = lsm330_read_register(priv, LSM330_GYRO_CTRL_REG3); + sninfo("LSM330_GYRO_CTRL_REG3 = 0x%02x\n", reg_content); + + reg_content = lsm330_read_register(priv, LSM330_GYRO_CTRL_REG4); + sninfo("LSM330_GYRO_CTRL_REG4 = 0x%02x\n", reg_content); + + reg_content = lsm330_read_register(priv, LSM330_GYRO_CTRL_REG5); + sninfo("LSM330_GYRO_CTRL_REG5 = 0x%02x\n", reg_content); +#endif + } + + priv->seek_address = (uint8_t) LSM330_GYRO_OUT_X_L; + return OK; +} + +/**************************************************************************** + * Name: lsm330acl_dvr_close + ******************************************************************************/ + +static int lsm330acl_dvr_close(FAR void *instance_handle, int32_t arg) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + + DEBUGASSERT(priv != NULL); + UNUSED(arg); + + /* Perform a reset to put the sensor in standby mode. */ + + lsm330acl_reset(priv); + + /* Release the sensor */ + + nxsem_post(&priv->devicesem); + return OK; +} + +/**************************************************************************** + * Name: lsm330gyro_dvr_close + ******************************************************************************/ + +static int lsm330gyro_dvr_close(FAR void *instance_handle, int32_t arg) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + + sninfo("lsm330gyro_close: entered...\n"); + + DEBUGASSERT(priv != NULL); + UNUSED(arg); + + /* Perform a reset to put the sensor in standby mode. */ + + lsm330gyro_reset(priv); + + /* Release the sensor */ + + nxsem_post(&priv->devicesem); + return OK; +} + +/**************************************************************************** + * Name: lsm330acl_dvr_read + ****************************************************************************/ + +static ssize_t lsm330acl_dvr_read(FAR void *instance_handle, + FAR char *buffer, size_t buflen) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + + DEBUGASSERT(priv != NULL); + + lsm330_read_acl_registerblk(priv, priv->seek_address, (uint8_t *)buffer, + buflen); + return buflen; +} + +/**************************************************************************** + * Name: lsm330gyro_dvr_read + ****************************************************************************/ + +static ssize_t lsm330gyro_dvr_read(FAR void *instance_handle, FAR char *buffer, + size_t buflen) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + + DEBUGASSERT(priv != NULL); + + lsm330_read_gyro_registerblk(priv, priv->seek_address, (uint8_t *)buffer, + buflen); + return buflen; +} + +/**************************************************************************** + * Name: lsm330acl_dvr_write + ****************************************************************************/ + +static ssize_t lsm330acl_dvr_write(FAR void *instance_handle, + FAR const char *buffer, size_t buflen) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + + DEBUGASSERT(priv != NULL); + + if (priv->readonly) + { + set_errno(-EROFS); + return -1; + } + + lsm330_write_acl_registerblk(priv, priv->seek_address, (uint8_t *)buffer, + buflen); + return buflen; +} + +/**************************************************************************** + * Name: lsm330gyro_dvr_write + ****************************************************************************/ + +static ssize_t lsm330gyro_dvr_write(FAR void *instance_handle, + FAR const char *buffer, size_t buflen) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + + DEBUGASSERT(priv != NULL); + + if (priv->readonly) + { + set_errno(-EROFS); + return -1; + } + + lsm330_write_gyro_registerblk(priv, priv->seek_address, + (FAR uint8_t *)buffer, buflen); + return buflen; +} + +/**************************************************************************** + * Name: lsm330acl_dvr_seek + ****************************************************************************/ + +static off_t lsm330acl_dvr_seek(FAR void *instance_handle, off_t offset, int whence) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + off_t reg; + + DEBUGASSERT(priv != NULL); + + switch (whence) + { + case SEEK_CUR: /* incremental seek */ + reg = priv->seek_address + offset; + if (0 > reg || reg > LSM330_ACL_LAST) + { + set_errno(-EINVAL); + return -1; + } + + priv->seek_address = reg; + break; + + case SEEK_END: /* seek to the 1st X-data register */ + priv->seek_address = LSM330_ACL_OUT_X_L; + break; + + case SEEK_SET: /* seek to designated address */ + if (0 > offset || offset > LSM330_ACL_LAST) + { + set_errno(-EINVAL); + return -1; + } + + priv->seek_address = offset; + break; + + default: /* Invalid whence */ + set_errno(-EINVAL); + return -1; + } + + return priv->seek_address; +} + +/**************************************************************************** + * Name: lsm330gyro_dvr_seek + ****************************************************************************/ + +static off_t lsm330gyro_dvr_seek(FAR void *instance_handle, off_t offset, + int whence) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + off_t reg; + + DEBUGASSERT(priv != NULL); + + switch (whence) + { + case SEEK_CUR: /* incremental seek */ + reg = priv->seek_address + offset; + if (0 > reg || reg > LSM330_GYRO_LAST) + { + set_errno(-EINVAL); + return -1; + } + + priv->seek_address = reg; + break; + + case SEEK_END: /* seek to the 1st data register */ + priv->seek_address = LSM330_GYRO_OUT_X_L; + break; + + case SEEK_SET: /* seek to designated address */ + if (0 > offset || offset > LSM330_GYRO_LAST) + { + set_errno(-EINVAL); + return -1; + } + + priv->seek_address = offset; + break; + + default: /* Invalid whence */ + set_errno(-EINVAL); + return -1; + } + + return priv->seek_address; +} + +/**************************************************************************** + * Name: lsm330_dvr_ioctl + ****************************************************************************/ + +static int lsm330_dvr_ioctl(FAR void *instance_handle, int cmd, + unsigned long arg) +{ + int ret = OK; + + switch (cmd) + { + /* Command was not recognized */ + + default: + snerr("ERROR: Unrecognized cmd: %d\n", cmd); + ret = -ENOTTY; + break; + } + + return ret; +} + +/**************************************************************************** + * Name: lsm330_dvr_exchange (with SPI DMA capability) + * + * Description: + * Exchange a block of data on SPI using DMA + * + * Input Parameters: + * instance_handle - Pointer to struct lsm330_dev_s. + * txbuffer - A pointer to the buffer of data to be sent + * rxbuffer - A pointer to a buffer in which to receive data + * nwords - the length of data to be exchanged in units of words. + * The wordsize is determined by the number of bits-per-word + * selected for the SPI interface. If nbits <= 8, the data is + * packed into uint8_t's; if nbits >8, the data is packed into + * uint16_t's + * + * Returned Value: + * None + * + ******************************************************************************/ + +static void lsm330_dvr_exchange(FAR void *instance_handle, + FAR const void *txbuffer, + FAR void *rxbuffer, size_t nwords) +{ + FAR struct lsm330_dev_s *priv = (FAR struct lsm330_dev_s *)instance_handle; + FAR struct spi_dev_s *spi = priv->spi; + + sninfo("In lsm330_dvr_exchange: Handle=0x%08X\n", instance_handle); + + /* Lock the SPI bus so that only one device can access it at the same time */ + + SPI_LOCK(spi, true); + + SPI_SETFREQUENCY(spi, LSM330_SPI_FREQUENCY); + SPI_SETMODE(spi, LSM330_SPI_MODE); + + sninfo("Calling SPI_EXCHANGE: devid=0x%08X\n", priv->config->spi_devid); + + /* Set CS to low which selects the LSM330 */ + + SPI_SELECT(spi, priv->config->spi_devid, true); + + /* Perform an SPI exchange block operation. */ + + SPI_EXCHANGE(spi, txbuffer, rxbuffer, nwords); + + /* Set CS to high to deselect the LSM330 */ + + SPI_SELECT(spi, priv->config->spi_devid, false); + + sninfo("Returned from : SPI_EXCHANGE\n"); + + /* Unlock the SPI bus */ + + SPI_LOCK(spi, false); +} + +/**************************************************************************** + * Name: lsm330acl_open + ****************************************************************************/ + +static int lsm330acl_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + int ret; + + ret = lsm330acl_dvr_open((FAR void *)priv, 0); + return ret; +} + +/**************************************************************************** + * Name: lsm330gyro_open + ****************************************************************************/ + +static int lsm330gyro_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + int ret; + + ret = lsm330gyro_dvr_open((FAR void *)priv, 0); + return ret; +} + +/**************************************************************************** + * Name: lsm330acl_close + ****************************************************************************/ + +static int lsm330acl_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + int ret; + + ret = lsm330acl_dvr_close((FAR void *)priv, 0); + return ret; +} + +/**************************************************************************** + * Name: lsm330gyro_close + ****************************************************************************/ + +static int lsm330gyro_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + int ret; + + ret = lsm330gyro_dvr_close((FAR void *)priv, 0); + return ret; +} + +/**************************************************************************** + * Name: lsm330acl_read + ****************************************************************************/ + +static ssize_t lsm330acl_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + + return lsm330acl_dvr_read(priv, buffer, buflen); +} + +/**************************************************************************** + * Name: lsm330gyro_read + ****************************************************************************/ + +static ssize_t lsm330gyro_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + + return lsm330gyro_dvr_read(priv, buffer, buflen); +} + +/**************************************************************************** + * Name: lsm330acl_write + ****************************************************************************/ + +static ssize_t lsm330acl_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + + return lsm330acl_dvr_write(priv, buffer, buflen); +} + +/**************************************************************************** + * Name: lsm330gyro_write + ****************************************************************************/ + +static ssize_t lsm330gyro_write(FAR struct file *filep, + FAR const char *buffer, size_t buflen) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + + return lsm330gyro_dvr_write(priv, buffer, buflen); +} + +/**************************************************************************** + * Name: lsm330acl_seek + ****************************************************************************/ + +static off_t lsm330acl_seek(FAR struct file *filep, off_t offset, int whence) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + + return lsm330acl_dvr_seek(priv, offset, whence); +} + +/**************************************************************************** + * Name: lsm330gyro_seek + ****************************************************************************/ + +static off_t lsm330gyro_seek(FAR struct file *filep, off_t offset, int whence) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + + return lsm330gyro_dvr_seek(priv, offset, whence); +} + +/**************************************************************************** + * Name: lsm330_ioctl + ****************************************************************************/ + +static int lsm330_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct lsm330_dev_s *priv = inode->i_private; + + return lsm330_dvr_ioctl(priv, cmd, arg); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lsm330_register + * + * Description: + * Register the LSM330 character device as 'devpath' + * + * Input Parameters: + * devpath_acl - The full path to the driver to register. E.g., + * "/dev/acl0" + * devpath_gyro - The full path to the driver to register. E.g., + * "/dev/gyr0" + * spi - An instance of the SPI interface to use to communicate + * with LSM330 + * config_acl - configuration for the LSM330 accelerometer driver. + * For details see description above. + * config_gyro - configuration for the LSM330 gyroscope driver. + * For details see description above. + * caller_is_driver - 0 (false) Driver user is a user application using + * the fops interface. + * 1 (true) "Driver to Driver" interface will be used + * in addition to the fops interface. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int lsm330_register(FAR const char *devpath_acl, + FAR const char *devpath_gyro, + FAR struct spi_dev_s *spi, + FAR struct lsm330_config_s *config_acl, + FAR struct lsm330_config_s *config_gyro) +{ + FAR struct lsm330_dev_s *priva; + FAR struct lsm330_dev_s *priv; + int ret; + + /* Sanity check */ + + DEBUGASSERT(spi != NULL); + DEBUGASSERT(config_acl != NULL); + DEBUGASSERT(config_gyro != NULL); + + config_acl->leaf_handle = NULL; + config_gyro->leaf_handle = NULL; + config_acl->sc_ops = NULL; + config_gyro->sc_ops = NULL; + + /* Initialize the LSM330 accelerometer device structure. */ + + priv = (FAR struct lsm330_dev_s *)kmm_malloc(sizeof(struct lsm330_dev_s)); + if (priv == NULL) + { + snerr("ERROR: Failed to allocate accelerometer instance\n"); + return -ENOMEM; + } + + priv->spi = spi; + priv->config = config_acl; + + /* Initialize sensor and sensor data access semaphore */ + + nxsem_init(&priv->devicesem, 0, 1); + nxsem_init(&priv->datasem, 0, 1); + + /* Setup SPI frequency and mode */ + + SPI_SETFREQUENCY(spi, LSM330_SPI_FREQUENCY); + SPI_SETMODE(spi, LSM330_SPI_MODE); + + /* Register the character driver */ + + ret = register_driver(devpath_acl, &g_lsm330a_fops, 0666, priv); + if (ret < 0) + { + snerr("ERROR: Failed to register accelerometer driver: %d\n", ret); + + nxsem_destroy(&priv->datasem); + kmm_free(priv); + return ret; + } + + /* Since we support multiple LSM330 devices, we will need to add this new + * instance to a list of device instances so that it can be found by the + * interrupt handler based on the received IRQ number. + */ + + priv->flink = g_lsm330a_list; + g_lsm330a_list = priv; + priva = priv; + config_acl->leaf_handle = (void *) priv; + + /* Initialize the LSM330 gyroscope device structure. */ + + priv = (FAR struct lsm330_dev_s *)kmm_malloc(sizeof(struct lsm330_dev_s)); + if (priv == NULL) + { + snerr("ERROR: Failed to allocate gyroscope instance\n"); + ret = -ENOMEM; + goto err_exit; + } + + priv->spi = spi; + priv->config = config_gyro; + + /* Initialize sensor and sensor data access semaphore */ + + nxsem_init(&priv->devicesem, 0, 1); + nxsem_init(&priv->datasem, 0, 1); + + /* Register the character driver */ + + ret = register_driver(devpath_gyro, &g_lsm330g_fops, 0666, priv); + if (ret < 0) + { + snerr("ERROR: Failed to register gyroscope driver: %d\n", ret); + + nxsem_destroy(&priv->datasem); + kmm_free(priv); + goto err_exit; + } + + /* Since we support multiple LSM330 devices, we will need to add this new + * instance to a list of device instances so that it can be found by the + * interrupt handler based on the received IRQ number. + */ + + priv->flink = g_lsm330g_list; + g_lsm330g_list = priv; + config_gyro->leaf_handle = (void *) priv; + + config_acl->sc_ops = &g_lsm330acl_dops; + config_gyro->sc_ops = &g_lsm330gyro_dops; + + /* If this is part of a kernel controlled sensor cluster driver, + * then return a handle to the caller + */ + + return OK; + +err_exit: + /* Registration the of the gyroscope failed, so we need to destroy the + * accelerometer instance. + */ + + nxsem_destroy(&priva->datasem); + kmm_free(priva); + return ret; +} + +#endif /* CONFIG_SPI && CONFIG_SENSORS_LSM330SPI && CONFIG_SPI_EXCHANGE */ diff --git a/include/nuttx/sensors/adxl372.h b/include/nuttx/sensors/adxl372.h new file mode 100644 index 00000000000..d86701d9a7c --- /dev/null +++ b/include/nuttx/sensors/adxl372.h @@ -0,0 +1,288 @@ +/****************************************************************************** + * include/nuttx/sensors/adxl372.h + * + * Copyright (C) 2017-2018 RAF Research LLC. All rights reserved. + * Author: Bob Feretich + * + * 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. + * + ******************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SENSORS_ADXL372_H +#define __INCLUDE_NUTTX_SENSORS_ADXL372_H + +/****************************************************************************** + * Driver usage notes: + * + * This driver is a "kernel sensor leaf driver" that may be used directly from + * user applications via the file_operations interface or have selected entry + * points called directly from a "kernel sensor cluster driver". + * + * To use this driver via the file_operations interface, the board + * initialization function should call this driver's registration function. + * The driver will register itself with Nuttx under the /dev path that is + * provided by the config structure. Then user applications may access the + * driver via the "file descriptor handle" returned by the file_operations + * open() function. + * + * By default the open() function configures the sensor for: + * + * Output Data Rate (ODR) = 1600 Hz. + * Bandwidth (BW) = 800 Hz. + * Normal mode sampling (as opposed to low power mode sampling). + * The Low Pass Filter is enabled and the High Pass Filter is disabled. + * A filter settling time of 370ms is selected. + * + * If the user desires a different configuration settings, the the user may + * either provide a pointer to an array of "struct adxl372_reg_pair_s" that + * will be applied to to the sensor upon open(); or dynamically use + * the lseek() and write() file_operations functions to set the + * sensor configuration as desired. + * + * When using the sensor from the file_operations interface, the sensor is + * accessed in Programmed I/O (PIO) mode. (i.e. When the read() function is + * executed, the sensor is read on that thread.) PIO reads and writes block + * the calling thread until data is available. Since the sensor is on an SPI + * bus running at near 10 MHz, the read or write operations should only take + * a few microseconds (about a microsecond per byte of data), so for + * individual sensor reads and writes, the overhead of using interrupts or + * DMA is not worthwhile. + * + * This driver supports the Common Sensor Register Interface. + * See drivers/sensors/README.txt for details. + * + * This driver supports the Sensor Cluster Driver Interface. + * See drivers/sensors/README.txt for details. + * + * It also extends the interface by permitting cluster driver calls to + * a function that is intended to perform high performance DMA SPI exchange + * operations. See the usage note on the exchange operation below. + * + ****************************************************************************/ + +/******************************************************************************* + * Included Files + ******************************************************************************* + */ + +#include +#include +#include +#include +#include + +#if defined(CONFIG_SPI) && defined(CONFIG_SENSORS_ADXL372) \ + && defined(CONFIG_SPI_EXCHANGE) + +/******************************************************************************* + * Pre-processor Definitions + ******************************************************************************* + */ + +/* ADXL372 common definitions */ + +#define ADXL372_READ 0x01 +#define ADXL372_WRITE 0x00 +#define ADXL372_ADDR_MASK 0x0fe + +/* ADXL372 Accelerometer Register definitions */ + +#define ADXL372_DEVID_AD (0x00 << 1) +# define ADXL372_DEVID_AD_VALUE 0xad +#define ADXL372_DEVID_MST (0x01 << 1) +# define ADXL372_DEVID_MST_VALUE 0x1D +#define ADXL372_PARTID (0x02 << 1) +# define ADXL372_PARTID_VALUE 0xfa +#define ADXL372_REVID (0x03 << 1) +#define ADXL372_STATUS (0x04 << 1) +#define ADXL372_STATUS2 (0x05 << 1) +#define ADXL372_FIFO_ENTRIES2 (0x06 << 1) +#define ADXL372_FIFO_ENTRIES (0x07 << 1) +#define ADXL372_XDATA_H (0x08 << 1) +#define ADXL372_XDATA_L (0x09 << 1) +#define ADXL372_YDATA_H (0x0a << 1) +#define ADXL372_YDATA_L (0x0b << 1) +#define ADXL372_ZDATA_H (0x0c << 1) +#define ADXL372_ZDATA_L (0x0d << 1) +#define ADXL372_THRESH_ACT_X_H (0x23 << 1) +#define ADXL372_FIFO_CTL (0x3a << 1) +# define ADXL372_FIFO_BYPASSED 0x00 +# define ADXL372_FIFO_STREAMED 0x02 +#define ADXL372_INT1_MAP (0x3b << 1) +# define ADXL372_INT1_MAP_DR 0x01 +# define ADXL372_INT1_MAP_FRDY 0x02 +# define ADXL372_INT1_MAP_FFULL 0x04 +#define ADXL372_TIMING (0x3d << 1) +# define ADXL372_TIMING_ODR400 (0x0 << 5) /* 400 Hz ODR */ +# define ADXL372_TIMING_ODR800 (0x1 << 5) /* 800 Hz ODR */ +# define ADXL372_TIMING_ODR1600 (0x2 << 5) /* 1600 Hz ODR */ +# define ADXL372_TIMING_ODR3200 (0x3 << 5) /* 3200 Hz ODR */ +# define ADXL372_TIMING_ODR6400 (0x4 << 5) /* 6400 Hz ODR */ +#define ADXL372_MEASURE (0x3e << 1) +# define ADXL372_MEAS_BW200 0x0 /* 200 Hz Bandwidth */ +# define ADXL372_MEAS_BW400 0x1 /* 400 Hz Bandwidth */ +# define ADXL372_MEAS_BW800 0x2 /* 800 Hz Bandwidth */ +# define ADXL372_MEAS_BW1600 0x3 /* 1600 Hz Bandwidth */ +# define ADXL372_MEAS_BW3200 0x4 /* 3200 Hz Bandwidth */ +#define ADXL372_POWER_CTL (0x3f << 1) +# define ADXL372_POWER_LPF_DISABLE (1 << 3) +# define ADXL372_POWER_HPF_DISABLE (1 << 2) +# define ADXL372_POWER_MODE_STANDBY 0x0 +# define ADXL372_POWER_MODE_WAKEUP 0x1 +# define ADXL372_POWER_MODE_INSTON 0x2 +# define ADXL372_POWER_MODE_MEASURE 0x3 +#define ADXL372_RESET (0x41 << 1) +# define ADXL372_RESET_VALUE 0x52 +#define ADXL372_FIFO_DATA (0x42 << 1) +#define ADXL372_LAST (0x42 << 1) +#define ADXL372_SCRATCH ADXL372_THRESH_ACT_X_H + +/* SPI Bus Parameters */ + +#define ADXL372_SPI_FREQUENCY (10000000) /* 10 MHz */ +#define ADXL372_SPI_MODE (SPIDEV_MODE0) /* SPI Mode 0: CPOL=0,CPHA=0 */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* A reference to a structure of this type must be passed to the ADXL372 + * driver. This structure provides information about the configuration + * of the sensor and provides some board-specific hooks. + * + * This sensor driver presents two interfaces, the POSIX character driver + * interface (fops) that is intended for use from a user application, and + * a set of direct call entry points that are intended to be used by + * a sensor cluster driver that is running as a kernel task (a driver to + * driver interface). Application tasks should not attempt to call sensor + * cluster driver entry points. + * + * Memory for this structure is provided by the caller. It is not copied + * by the driver and is presumed to persist while the driver is active. + */ + +struct adxl372_reg_pair_s /* Utility struct for the below... */ +{ + uint8_t addr; /* SPI register address */ + uint8_t value; /* Value to be stored in the above reg on open() */ +}; + +struct adxl372_dvr_entry_vector_s +{ + struct sensor_cluster_operations_s c; + + /* Extend the sensor cluster driver interface with a SPI DMA exchange transfer. + * The standard driver_read and driver_write perform PIO transfers. + * The will loop waiting on the SPI hardware and are only appropriate for + * short data transfers. + * Note that the first byte in the tx buffer must be a command/address + * byte. The exchange function does not provide one. Also note that + * the first byte stored in the rxbuffer is a garbage byte, which + * is natural for a SPI exchange transfer. Plan your buffer accordingly. + */ + + CODE void (*driver_spiexc)(FAR void *instance_handle, + FAR const void *txbuffer, + FAR void *rxbuffer, size_t nwords); +}; + +struct adxl372_config_s +{ + /* Since multiple ADXL372 can be connected to the same SPI bus we need + * to use multiple SPI device ids which are employed by NuttX to select/ + * deselect the desired ADXL372 chip via their chip select inputs. + */ + + int spi_devid; + + /* Initial control register configuration values. */ + + uint16_t initial_cr_values_size; /* size of the below array. + * 0 = use default values. */ + + /* The initial value store operations will occur in the order they + * appear in the array. + */ + + struct adxl372_reg_pair_s *initial_cr_values; + + /* The below fields are intended for the sensor cluster driver interface + * and may be ignored when the sensor cluster driver is not being used. + * The leaf driver's registration function fills in the below fields. + */ + + /* Leaf sensor handle for sensor cluster kernel driver */ + + FAR struct spi_dev_s *leaf_handle; + + /* Pointer to the leaf driver's sensor_cluster_operations_s structure */ + + FAR const struct adxl372_dvr_entry_vector_s *sc_ops; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: adxl372_register + * + * Description: + * Register the ADXL372 character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/acl0" + * spi - An instance of the SPI interface to use to communicate with + * ADXL372 + * config - configuration for the ADXL372 accelerometer driver. For + * details see description above. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int adxl372_register(FAR const char *devpath, + FAR struct spi_dev_s *spi, + FAR struct adxl372_config_s *config); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_SPI && CONFIG_SENSORS_ADXL372 && CONFIG_SPI_EXCHANGE */ +#endif /* __INCLUDE_NUTTX_SENSORS_ADXL372_H */ diff --git a/include/nuttx/sensors/cluster_driver.h b/include/nuttx/sensors/cluster_driver.h new file mode 100644 index 00000000000..49cfc8ab692 --- /dev/null +++ b/include/nuttx/sensors/cluster_driver.h @@ -0,0 +1,192 @@ +/**************************************************************************** + * include/nuttx/sensors/cluster_driver.h + * + * Copyright (C) 2017 RAF Research LLC. All rights reserved. + * Author: Bob Feretich + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SENSORS_CLUSTER_DRIVER_H +#define __INCLUDE_NUTTX_SENSORS_CLUSTER_DRIVER_H + +/* Definitions for the cluster driver interface: + * + * The sensor cluster driver interface is created to permit high performance + * collection and processing of sensor data. Normally when high performance + * sensor data collection is required, data is needed from multiple sensors + * and the collection/processing task needs to run as a "higher than user + * priority" priority level. This mechanism is commonly supported by writing + * the collection/processing task as a kernel driver worker thread that can + * access multiple sensors. + * + * A clean way to implement this is to implement this collection/processing + * mechanism is as a driver that has efficient kernel-to-kernel access to + * other sensor leaf-drivers that support the cluster driver interface. + * (This documentation describes the "cluster driver interface" from the + * perspective of a "cluster driver interface" enabled leaf driver.) + * + * The file_operations interface typically deals with the caller's parameters + * and buffers being in user space, while the driver's code, variables, and + * buffers are in kernel space. There is also a layer of kernel code between + * the caller and driver that deals with security and assists helps with the + * driver's access of user space memory. So the file_operations interface + * is not appropriate for driver-to-driver communication. + * + * Since the driver registration function is not part of the file_operations + * interface and is permitted to be called from a kernel task, this function + * is reused. But rather than being called by the board initialization + * function, the cluster drivers registration function is called from the + * board initialization function; and the cluster drivers registration function + * calls the leaf driver's registration function. + * + * To be "cluster driver interface" enabled the leaf driver's registration + * function must communicate the leaf driver's instance back to the cluster + * driver's registration function. This is done by storing the leaf + * driver's instance handle into the caller provided config structure. + * This handle is provided as a parameter in cluster driver interface calls. + * + * dvr_open(): Reserve this sensor. + * + * dvr_close(): Places the sensor into low power standby mode, frees driver + * resources associated with the sensor, and release the reservation + * . of the sensor. + * + * To perform I/O to a sensor, the cluster driver needs... + * > A pointer to the spi instance (struct spi_dev_s *). The cluster driver + * has this pointer, because it provides it to the leaf driver as an input + * parameter to the leaf driver's registration function. + * > The chip select gpio pin identifier for the sensor device. The cluster + * driver knows this identifier,because it provides it to the leaf driver + * as an input parameter to the leaf driver's registration function. + * > A pointer to the leaf driver instance. The leaf driver communicates this + * pointer to the cluster driver by storing it into the config struct + * leaf_handle (struct spi_dev_s *) field that is also passed as an in/out + * parameter in the leaf driver's registration function. + * + * With the above information, the sensor cluster driver may efficiently access + * multiple sensors and aggregate their data. + * + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Cluster driver operations interface */ + +struct sensor_cluster_operations_s +{ + CODE int (*driver_open)(FAR void *instance_handle, int32_t arg); + CODE int (*driver_close)(FAR void *instance_handle, int32_t arg); + CODE ssize_t (*driver_read)(FAR void *instance_handle, FAR char *buffer, + size_t buflen); + CODE ssize_t (*driver_write)(FAR void *instance_handle, + FAR const char *buffer, size_t buflen); + CODE off_t (*driver_seek)(FAR void *instance_handle, off_t offset, + int whence); + CODE int (*driver_ioctl)(FAR void *instance_handle, int cmd, + unsigned long arg); + CODE int (*driver_suspend)(FAR void *instance_handle, int32_t arg); + CODE int (*driver_resume)(FAR void *instance_handle, int32_t arg); +}; + +/************************************************************************** + * Public Function Prototypes + **************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/***************************************************************************** + * Name: xxxxxx_register <-- for a leaf driver. + * + * Description: + * Example of a driver register function for a "sensor leaf driver" that + * can be controlled by a "sensor cluster driver". The specific format of + * the registration function will vary based on the characteristics of the + * driver. The below is an example, which describes common aspects of the + * function and and its parameters. + * + * Normally, sensor driver register functions are called by the board + * initialization code. In the case of a "sensor cluster driver", the board + * initialization code calls the register function of the "sensor cluster + * driver"; and the "sensor cluster driver" calls the register function of + * each of the sensor (leaf) drivers that it will be controlling. + * + * Input Parameters: + * devpath - The full path to the leaf driver to register. E.g., + * "/dev/acl0" + * spi - An instance of the SPI interface to use to communicate with the + * leaf driver. Or, it could be the I2C driver instance if the + * sensor is on an I2C bus. + * config - configuration struct for the sensor leaf driver. This struct is + * defined in the leaf driver's xxxxxx.h file. + * + * For a SPI sensor, this structure must contain: + * + * int spi_devid; The spi device id, which is used by NuttX to + * select/deselect the device. This is usually some + * type of reference to the chip_select gpio pin. + * FAR void *leaf_handle; The handle to the leaf driver instance. + * This is an opaque handle that provided by the + * leaf driver's register function. + * It is passed as a parameter to the leaf driver's + * driver_open() and driver_close() functions. + * + * For an I2C sensor, this struct must contain: + * + * int i2c_devid; The i2c device id, which is used by NuttX to + * address the sensor device on an I2C bus.. + * FAR void *leaf_handle; The handle to the leaf driver instance. + * This is an opaque handle that provided by the + * leaf driver's register function. + * It is passed as a parameter to the leaf driver's + * driver_open() and driver_close() functions. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + * int xxxxxx_register(FAR const char *devpath, + * FAR struct spi_dev_s *spi, + * FAR struct xxxxxx_config_s *config); + * + ****************************************************************************/ + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_SENSORS_CLUSTER_DRIVER_H */ diff --git a/include/nuttx/sensors/lsm330.h b/include/nuttx/sensors/lsm330.h new file mode 100644 index 00000000000..5a65b1a388d --- /dev/null +++ b/include/nuttx/sensors/lsm330.h @@ -0,0 +1,355 @@ +/***************************************************************************** + * include/nuttx/sensors/lsm330.h + * + * Copyright (C) 2017-2018 RAF Research LLC. All rights reserved. + * Author: Bob Feretich + * + * 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. + * + *****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SENSORS_LSM330_H +#define __INCLUDE_NUTTX_SENSORS_LSM330_H + +/***************************************************************************** + * Driver usage notes: + * + * This driver is a "kernel sensor leaf driver" that may be used directly + * from user applications via the file_operations interface or have selected + * entry points called directly from a "kernel sensor cluster driver". + * + * To use this driver via the file_operations interface, the board + * initialization function should call this driver's registration function. + * The driver will register itself with Nuttx under the /dev path that is + * provided by the config structure. Then user applications may access the + * driver via the "file descriptor handle" returned by the file_operations + * open() function. + * + * By default the accelerometer's open() function configures the sensor + * for: + * + * Output Data Rate (ODR) = 1600 Hz. + * Bandwidth (BW) = 800 Hz. + * Range = 16g. + * + * By default the gyroscope's open() function configures the sensor for: + * + * Output Data Rate (ODR) = 760 Hz. + * Bandwidth (BW) = 100 Hz. + * Range = 500 dps. + * Low Pass Filter #1 selected. + * + * If the user desires a different configuration settings, the the user may + * either provide a pointer to an array of "struct lsm330_reg_pair_s" that + * will be applied to to the sensor upon open(); or dynamically use the + * lseek() and write() file_operations functions to set the sensor + * configuration as desired. + * + * When using the sensor from the file_operations interface, the sensor is + * accessed in Programmed I/O (PIO) mode. (i.e. When the read() function is + * executed, the sensor is read on that thread.) PIO reads and writes block + * the calling thread until data is available. Since the sensor is on an SPI + * bus running at near 10 MHz, the read or write operations should only take + * a few microseconds (about a microsecond per byte of data), so for + * individual sensor reads and writes, the overhead of using interrupts or + * DMA is not worthwhile. + * + * Use the Cluster Driver Interface to perform DMA block transfers. + * + * This driver supports the Common Sensor Register Interface. + * See drivers/sensors/README.txt for details. + * + * This driver supports the Sensor Cluster Driver Interface. + * See drivers/sensors/README.txt for details. + * + * It also extends the interface by permitting cluster driver calls to + * a function that is intended to perform high performance DMA SPI exchange + * operations. See the usage note on the exchange operation below. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* LSM330 common definitions */ + +#define LSM330_READ 0x80 +#define LSM330_WRITE 0x00 +#define LSM330_GYRO_AUTO 0x40 + +/**************************************************************************** + * LSM330 Accelerometer Register definitions + ****************************************************************************/ + +#define LSM330_ACL_IDREG 0x0f +#define LSM330_ACL_IDREG_VALUE 0x40 + +#define LSM330_ACL_CTRL_REG2 0x21 +#define LSM330_ACR2_HYST1_MASK 0xe0 /* Hysteresis for SM1 bit mask */ +#define LSM330_ACR2_HYST1_SHIFT 5 /* Hysteresis for SM1 shift amount */ +#define LSM330_ACR2_SM1_PIN 0x08 /* 1= SM1 int on INT2_A; 0= on INT1_A */ +#define LSM330_ACR2_SM1_EN 0x01 /* 1= SM1 enabled; 0= disabled */ + +#define LSM330_ACL_CTRL_REG3 0x22 +#define LSM330_ACR3_HYST2_MASK 0xe0 /* Hysteresis for SM2 bit mask */ +#define LSM330_ACR3_HYST2_SHIFT 5 /* Hysteresis for SM2 shift amount */ +#define LSM330_ACR3_SM2_PIN 0x08 /* 1= SM2 int on INT1_A; 0= on INT1_A */ +#define LSM330_ACR3_SM2_EN 0x01 /* 1= SM2 enabled; 0= disabled */ + +#define LSM330_ACL_CTRL_REG4 0x23 +#define LSM330_ACR4_DREN 0x80 /* 1= Data Ready enabled on INT1_A; 0= disabled */ +#define LSM330_ACR4_IEA 0x40 /* 1= INT1 polarity active high; 0= low */ +#define LSM330_ACR4_IEL 0x20 /* 1= INT1 pulsed; 0= latched */ +#define LSM330_ACR4_INT2_EN 0x10 /* 1= INT2_A enabled; 0= disabled */ +#define LSM330_ACR4_INT1_EN 0x08 /* 1= INT1_A enabled; 0= disabled */ +#define LSM330_ACR4_VFILT 0x40 /* 1= Vector filter enabled; 0= disabled */ +#define LSM330_ACR4_STRT 0x01 /* 1= Soft reset. Cleared by HW when done */ + +#define LSM330_ACL_CTRL_REG5 0x20 +#define LSM330_ACR5_ODR_MASK 0xf0 /* ODR bit mask */ +#define LSM330_ACR5_ODR_OFF 0x00 /* ODR = powered down */ +#define LSM330_ACR5_ODR_3 0x10 /* ODR = 3.125 Hz */ +#define LSM330_ACR5_ODR_6 0x20 /* ODR = 6.25 Hz */ +#define LSM330_ACR5_ODR_12 0x30 /* ODR = 12.5 Hz */ +#define LSM330_ACR5_ODR_25 0x40 /* ODR = 25 Hz */ +#define LSM330_ACR5_ODR_50 0x50 /* ODR = 50 Hz */ +#define LSM330_ACR5_ODR_100 0x60 /* ODR = 100 Hz */ +#define LSM330_ACR5_ODR_400 0x70 /* ODR = 400 Hz */ +#define LSM330_ACR5_ODR_800 0x80 /* ODR = 800 Hz */ +#define LSM330_ACR5_ODR_1600 0x90 /* ODR = 1600 Hz */ +#define LSM330_ACR5_BDU 0x08 /* 1= MSB not updated until LSB is read */ +#define LSM330_ACR5_ZEN 0x04 /* 1= Z-axis sensor enabled */ +#define LSM330_ACR5_YEN 0x02 /* 1= Y-axis sensor enabled */ +#define LSM330_ACR5_XEN 0x01 /* 1= X-axis sensor enabled */ + +#define LSM330_ACL_CTRL_REG6 0x24 +#define LSM330_ACR6_BW_MASK 0xc0 /* Bandwidth bit mask */ +#define LSM330_ACR6_BW_800 0x00 /* Bandwidth = 800 Hz */ +#define LSM330_ACR6_BW_200 0x40 /* Bandwidth = 200 Hz */ +#define LSM330_ACR6_BW_400 0x80 /* Bandwidth = 400 Hz */ +#define LSM330_ACR6_BW_50 0xc0 /* Bandwidth = 50 Hz */ +#define LSM330_ACR6_FS_MASK 0x38 /* Full Scale bit mask */ +#define LSM330_ACR6_FS_2 0x00 /* FScale = 2g */ +#define LSM330_ACR6_FS_4 0x08 /* FScale = 4g */ +#define LSM330_ACR6_FS_6 0x10 /* FScale = 6g */ +#define LSM330_ACR6_FS_8 0x18 /* FScale = 8g */ +#define LSM330_ACR6_FS_16 0x20 /* FScale = 16g */ +#define LSM330_ACR6_SIM 0x01 /* 1= 3-wire SPI; 0= 4-wire SPI */ + +#define LSM330_ACL_CTRL_REG7 0x25 +#define LSM330_ACR7_BOOT 0x80 /* Force reboot, cleared by HW when done */ +#define LSM330_ACR7_FIFO_EN 0x40 /* 1= FIFO enabled; 0= disabled */ +#define LSM330_ACR7_WTM_EN 0x20 /* 1= FIFO watermark enabled; 0= disabled */ +#define LSM330_ACR7_ADD_INC 0x10 /* 1= auto post-increment ACL addresses */ +#define LSM330_ACR7_P1_EMPTY 0x08 /* 1= enable FIFO empty on INT1_A */ +#define LSM330_ACR7_P1_WTM 0x04 /* 1= enable FIFO watermark on INT1_A */ +#define LSM330_ACR7_P1_OVERRUN 0x02 /* 1= enable FIFO overrun on INT1_A */ +#define LSM330_ACR7_P2_BOOT 0x01 /* 1= enable BOOT interrupt on INT2_A */ + +#define LSM330_ACL_STATUS 0x27 +#define LSM330_ASR_ZYXOR 0x80 /* 1= At least one of the axes has overrun */ +#define LSM330_ASR_ZOR 0x40 /* 1= Z-axis has overrun and lost data */ +#define LSM330_ASR_YOR 0x20 /* 1= Y-axis has overrun and lost data */ +#define LSM330_ASR_XOR 0x10 /* 1= X-axis has overrun and lost data */ +#define LSM330_ASR_ZYXDA 0x08 /* 1= All of the axes have new data */ +#define LSM330_ASR_ZDA 0x04 /* 1= New Z-axis data is available */ +#define LSM330_ASR_YDA 0x02 /* 1= New Y-axis data is available */ +#define LSM330_ASR_XDA 0x01 /* 1= New X-axis data is available */ + +#define LSM330_ACL_OUT_X_L 0x28 +#define LSM330_ACL_OUT_X_H 0x29 +#define LSM330_ACL_OUT_Y_L 0x2a +#define LSM330_ACL_OUT_Y_H 0x2b +#define LSM330_ACL_OUT_Z_L 0x2c +#define LSM330_ACL_OUT_Z_H 0x2d +#define LSM330_ACL_THRS1_1 0x57 +#define LSM330_ACL_LAST 0x7f +#define LSM330_ACL_SCRATCH LSM330_ACL_THRS1_1 + +/* LSM330 Gyroscope Register definitions */ + +#define LSM330_GYRO_IDREG 0x0f +# define LSM330_GYRO_IDREG_VALUE 0xd4 +#define LSM330_GYRO_CTRL_REG1 0x20 +#define LSM330_GYRO_CTRL_REG2 0x21 +#define LSM330_GYRO_CTRL_REG3 0x22 +#define LSM330_GYRO_CTRL_REG4 0x23 +#define LSM330_GYRO_CTRL_REG5 0x24 +# define LSM_GYRO_BOOT_MASK 0x80 +#define LSM330_GYRO_OUT_TEMP 0x26 +#define LSM330_GYRO_STATUS_REG 0x27 +#define LSM330_GYRO_OUT_X_L 0x28 +#define LSM330_GYRO_OUT_X_H 0x29 +#define LSM330_GYRO_OUT_Y_L 0x2a +#define LSM330_GYRO_OUT_Y_H 0x2b +#define LSM330_GYRO_OUT_Z_L 0x2c +#define LSM330_GYRO_OUT_Z_H 0x2d +#define LSM330_GYRO_INT1_THS_ZL 0x37 +#define LSM330_GYRO_LAST 0x3f +#define LSM330_GYRO_SCRATCH LSM330_GYRO_INT1_THS_ZL + +/* SPI Bus Parameters */ + +#define LSM330_SPI_FREQUENCY (10000000) /* 10 MHz */ +#define LSM330_SPI_MODE (SPIDEV_MODE3) /* SPI Mode 3: CPOL=1,CPHA=1 */ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* A reference to a structure of this type must be passed to the LSM330 + * driver. This structure provides information about the configuration + * of the sensor and provides some board-specific hooks. + * + * This sensor driver presents two interfaces, the POSIX character driver + * interface (fops) that is intended for use from a user application, and + * a set of direct call entry points that are intended to be used by + * a sensor cluster driver that is running as a kernel task (a driver to + * driver interface). Application tasks should not attempt to call sensor + * cluster driver entry points. + * + * Memory for this structure is provided by the caller. It is not copied + * by the driver and is presumed to persist while the driver is active. + */ + +struct lsm330_reg_pair_s /* Utility struct for the below... */ +{ + uint8_t addr; /* SPI register address */ + uint8_t value; /* Value to be stored in the above reg on open() */ +}; + +struct lsm330spi_dvr_entry_vector_s +{ + struct sensor_cluster_operations_s c; + + /* Extend the sensor cluster driver interface with a SPI DMA exchange + * transfer. The standard driver_read and driver_write perform PIO + * transfers. The will loop waiting on the SPI hardware and are only + * appropriate for short data transfers. + * + * Note that the first byte in the tx buffer must be a command/address + * byte. The exchange function does not provide one. Also note that + * the first byte stored in the rxbuffer is a garbage byte, which + * is natural for a SPI exchange transfer. Plan your buffer accordingly. + */ + + CODE void (*driver_spiexc)(FAR void *instance_handle, + FAR const void *txbuffer, + FAR void *rxbuffer, size_t nwords); +}; + +struct lsm330_config_s +{ + /* Since multiple LSM330 can be connected to the same SPI bus we need + * to use multiple spi device ids which are employed by NuttX to select/ + * deselect the desired LSM330 chip via their chip select inputs. + */ + + int spi_devid; + + /* Initial control register configuration values. */ + + uint16_t initial_cr_values_size; /* size of the below array. + * 0 = use default values. */ + + /* The initial value store operations will occur in the order they appear + * in the array. + */ + + struct lsm330_reg_pair_s *initial_cr_values; + + /* The below fields are intended for the sensor cluster driver interface + * and may be ignored when the sensor cluster driver is not being used. + * The leaf driver's registration function fills in the below fields. + */ + + /* Leaf sensor handle (opaque) for sensor cluster kernel driver */ + + FAR void *leaf_handle; + + /* Pointer to the leaf driver's sensor_cluster_operations_s structure */ + + FAR const struct lsm330spi_dvr_entry_vector_s *sc_ops; +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/******************************************************************************* + * Name: lsm330_register + * + * Description: + * Register the LSM330 character device as 'devpath' + * + * Input Parameters: + * devpath_acl - The full path to the driver to register. E.g., "/dev/acl0" + * devpath_gyro - The full path to the driver to register. E.g., "/dev/gyr0" + * spi - An instance of the SPI interface to use to communicate with LSM330 + * config_acl - configuration for the LSM330 accelerometer driver. + * For details see description above. + * config_gyro - configuration for the LSM330 gyroscope driver. + * For details see description above. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ******************************************************************************* + */ + +int lsm330_register(FAR const char *devpath_acl, + FAR const char *devpath_gyro, + FAR struct spi_dev_s *spi, + FAR struct lsm330_config_s *config_acl, + FAR struct lsm330_config_s *config_gyro); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_SENSORS_LSM330_H */