diff --git a/boards/arm/cxd56xx/common/CMakeLists.txt b/boards/arm/cxd56xx/common/CMakeLists.txt index 33c01757531..3525a62d206 100644 --- a/boards/arm/cxd56xx/common/CMakeLists.txt +++ b/boards/arm/cxd56xx/common/CMakeLists.txt @@ -189,5 +189,9 @@ if(CONFIG_ARCH_BOARD_COMMON) list(APPEND SRCS src/cxd56_gnss_addon.c) endif() + if(CONFIG_SENSORS_CXD5602PWBIMU) + list(APPEND SRCS src/cxd56_cxd5602pwbimu.c) + endif() + target_sources(board PRIVATE ${SRCS}) endif() diff --git a/boards/arm/cxd56xx/common/src/Make.defs b/boards/arm/cxd56xx/common/src/Make.defs index 76b45c5a0a8..360382169e1 100644 --- a/boards/arm/cxd56xx/common/src/Make.defs +++ b/boards/arm/cxd56xx/common/src/Make.defs @@ -190,6 +190,10 @@ ifeq ($(CONFIG_CXD56_GNSS_ADDON),y) CSRCS += cxd56_gnss_addon.c endif +ifeq ($(CONFIG_SENSORS_CXD5602PWBIMU),y) + CSRCS += cxd56_cxd5602pwbimu.c +endif + DEPPATH += --dep-path src VPATH += :src CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)$(CONFIG_ARCH)$(DELIM)src$(DELIM)board$(DELIM)src diff --git a/boards/arm/cxd56xx/common/src/cxd56_cxd5602pwbimu.c b/boards/arm/cxd56xx/common/src/cxd56_cxd5602pwbimu.c new file mode 100644 index 00000000000..8773a265ab2 --- /dev/null +++ b/boards/arm/cxd56xx/common/src/cxd56_cxd5602pwbimu.c @@ -0,0 +1,261 @@ +/**************************************************************************** + * boards/arm/cxd56xx/common/src/cxd56_cxd5602pwbimu.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include "cxd56_gpio.h" +#include "cxd56_gpioint.h" +#include "cxd56_spi.h" +#include "cxd56_dmac.h" +#include "cxd56_i2c.h" + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define PIN_POWER PIN_EMMC_DATA2 +#define PIN_XRST PIN_I2S0_BCK +#define PIN_SPI_DRDY PIN_EMMC_DATA3 +#define PIN_SPI_CSX PIN_I2S0_DATA_IN + +#define SETUP_PIN_INPUT(pin) \ +do \ + { \ + board_gpio_write(pin, -1); \ + board_gpio_config(pin, 0, true, false, PIN_PULLDOWN); \ + } \ +while (0) + +#define SETUP_PIN_OUTPUT(pin) \ +do \ + { \ + board_gpio_write(pin, -1); \ + board_gpio_config(pin, 0, false, true, PIN_FLOAT); \ + } \ +while (0) + +#if defined(CONFIG_CXD56_CXD5602PWBIMU_SPI5_DMAC) +#define PWBIMU_MAX_PACKET_SIZE (34) +#define PWBIMU_DMA_TXCH (4) +#define PWBIMU_DMA_RXCH (5) +#define PWBIMU_DMA_TXCHCHG (CXD56_DMA_PERIPHERAL_SPI5_TX) +#define PWBIMU_DMA_RXCHCFG (CXD56_DMA_PERIPHERAL_SPI5_RX) +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int cxd5602pwbimu_irq_attach(const cxd5602pwbimu_config_t *state, + xcpt_t isr, void *arg); +static void cxd5602pwbimu_irq_enable(const cxd5602pwbimu_config_t *state, + bool enable); +static void cxd5602pwbimu_csx(const cxd5602pwbimu_config_t *state, + bool pol); +static void cxd5602pwbimu_power(const cxd5602pwbimu_config_t *state, + bool pol); +static void cxd5602pwbimu_reset(const cxd5602pwbimu_config_t *state, + bool assert); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static cxd5602pwbimu_config_t g_config = +{ + .irq_attach = cxd5602pwbimu_irq_attach, + .irq_enable = cxd5602pwbimu_irq_enable, + .csx = cxd5602pwbimu_csx, + .power = cxd5602pwbimu_power, + .reset = cxd5602pwbimu_reset, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Attach the CXD5602PWBIMU interrupt handler to the GPIO interrupt */ + +static int cxd5602pwbimu_irq_attach(const cxd5602pwbimu_config_t *state, + xcpt_t isr, void *arg) +{ + sninfo("cxd5602pwbimu_irq_attach\n"); + cxd56_gpioint_config(PIN_SPI_DRDY, GPIOINT_LEVEL_HIGH, isr, arg); + + return OK; +} + +/* Enable or disable the GPIO interrupt */ + +static void cxd5602pwbimu_irq_enable(const cxd5602pwbimu_config_t *state, + bool enable) +{ + sninfo("%d\n", enable); + board_gpio_int(PIN_SPI_DRDY, enable); +} + +/* Toggle the csx pin level */ + +static void cxd5602pwbimu_csx(const cxd5602pwbimu_config_t *state, + bool pol) +{ + sninfo("%d\n", pol); + + /* delay for csx rising edge */ + + if (pol == true) + { + up_udelay(5); + } + + /* toggle csx pin */ + + board_gpio_write(PIN_SPI_CSX, pol); + + /* delay for csx falling edge */ + + if (pol == false) + { + up_udelay(5); + } +} + +static void cxd5602pwbimu_power(const cxd5602pwbimu_config_t *state, + bool pol) +{ + sninfo("%d\n", pol); + + /* toggle power pin */ + + board_gpio_write(PIN_POWER, pol); + + /* wait power level settling */ + + up_mdelay(2); +} + +static void cxd5602pwbimu_reset(const cxd5602pwbimu_config_t *state, + bool assert) +{ + board_gpio_write(PIN_XRST, !assert); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#if defined(CONFIG_CXD56_SPI) && defined(CONFIG_SENSORS_CXD5602PWBIMU) + +int board_cxd5602pwbimu_initialize(int bus) +{ + int ret; + struct spi_dev_s *spi; + struct i2c_master_s *i2c; +#if defined(CONFIG_CXD56_CXD5602PWBIMU_SPI5_DMAC) + DMA_HANDLE hdl; + dma_config_t conf; +#endif + cxd5602pwbimu_config_t *config = &g_config; + + sninfo("Initializing CXD5602PWBIMU..\n"); + + /* Initialize pins */ + + SETUP_PIN_OUTPUT(PIN_POWER); + + SETUP_PIN_OUTPUT(PIN_XRST); + config->reset(config, true); + + SETUP_PIN_OUTPUT(PIN_SPI_CSX); + config->csx(config, true); + + SETUP_PIN_INPUT(PIN_SPI_DRDY); + + /* Initialize spi device */ + + spi = cxd56_spibus_initialize(bus); + if (!spi) + { + snerr("ERROR: Failed to initialize spi%d.\n", bus); + return -ENODEV; + } + +#if defined(CONFIG_CXD56_CXD5602PWBIMU_SPI5_DMAC) + hdl = cxd56_dmachannel(PWBIMU_DMA_TXCH, + PWBIMU_MAX_PACKET_SIZE); + if (hdl) + { + conf.channel_cfg = PWBIMU_DMA_TXCHCHG; + conf.dest_width = CXD56_DMAC_WIDTH8; + conf.src_width = CXD56_DMAC_WIDTH8; + cxd56_spi_dmaconfig(bus, CXD56_SPI_DMAC_CHTYPE_TX, hdl, &conf); + } + + hdl = cxd56_dmachannel(PWBIMU_DMA_RXCH, + PWBIMU_MAX_PACKET_SIZE); + if (hdl) + { + conf.channel_cfg = PWBIMU_DMA_RXCHCFG; + conf.dest_width = CXD56_DMAC_WIDTH8; + conf.src_width = CXD56_DMAC_WIDTH8; + cxd56_spi_dmaconfig(bus, CXD56_SPI_DMAC_CHTYPE_RX, hdl, &conf); + } +#endif + + /* Initialize i2c device */ + + i2c = cxd56_i2cbus_initialize(0); + if (!i2c) + { + snerr("ERROR: Failed to initialize i2c%d.\n", 0); + return -ENODEV; + } + + ret = cxd5602pwbimu_register("/dev/imu0", spi, i2c, config); + if (ret < 0) + { + snerr("Error registering CXD5602PWBIMU\n"); + } + + return ret; +} + +#endif diff --git a/boards/arm/cxd56xx/spresense/Kconfig b/boards/arm/cxd56xx/spresense/Kconfig index e2b6d51c436..15b15edfc57 100644 --- a/boards/arm/cxd56xx/spresense/Kconfig +++ b/boards/arm/cxd56xx/spresense/Kconfig @@ -871,4 +871,23 @@ config CXD56_GNSS_ADDON_LATE_INITIALIZE endif # CXD56_GNSS_ADDON +menu "CXD5602PWBIMU Add-on board" + depends on SENSORS_CXD5602PWBIMU + +config CXD56_CXD5602PWBIMU_SPI5_DMAC + bool "Use DMAC on data transfer" + default y + select CXD56_SPI5 + select CXD56_DMAC_SPI5_TX + select CXD56_DMAC_SPI5_RX + +config CXD56_CXD5602PWBIMU_LATE_INITIALIZE + bool "CXD5602PWBIMU driver late initialization" + default n + ---help--- + The CXD5602PWBIMU driver can be initialized on an application code + after system booted up by enabling this configuration switch. + +endmenu # CXD5602PWBIMU Add-on board + endif diff --git a/boards/arm/cxd56xx/spresense/include/board.h b/boards/arm/cxd56xx/spresense/include/board.h index f2c3fcd8232..f5e0abfbb80 100644 --- a/boards/arm/cxd56xx/spresense/include/board.h +++ b/boards/arm/cxd56xx/spresense/include/board.h @@ -69,6 +69,7 @@ #include "cxd56_scd41.h" #include "cxd56_sensors.h" #include "cxd56_gnss_addon.h" +#include "cxd56_cxd5602pwbimu.h" #ifdef CONFIG_VIDEO_ISX012 # include "cxd56_isx012.h" diff --git a/boards/arm/cxd56xx/spresense/include/cxd56_cxd5602pwbimu.h b/boards/arm/cxd56xx/spresense/include/cxd56_cxd5602pwbimu.h new file mode 100644 index 00000000000..ac53ffcd8cf --- /dev/null +++ b/boards/arm/cxd56xx/spresense/include/cxd56_cxd5602pwbimu.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * boards/arm/cxd56xx/spresense/include/cxd56_cxd5602pwbimu.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __BOARDS_ARM_CXD56XX_SPRESENSE_INCLUDE_CXD56_CXD5602PWBIMU_H +#define __BOARDS_ARM_CXD56XX_SPRESENSE_INCLUDE_CXD56_CXD5602PWBIMU_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +#ifndef __ASSEMBLY__ + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#undef EXTERN +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: board_cxd5602pwbimu_initialize + * + * Description: + * Initialize CXD5602PWBIMU spi driver and register the CXD5602PWBIMU device. + * + ****************************************************************************/ + +int board_cxd5602pwbimu_initialize(int bus); + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ +#endif /* __BOARDS_ARM_CXD56XX_SPRESENSE_INCLUDE_CXD56_CXD5602PWBIMU_H */ diff --git a/boards/arm/cxd56xx/spresense/src/cxd56_bringup.c b/boards/arm/cxd56xx/spresense/src/cxd56_bringup.c index 1ddb27e508d..e6846a46e3f 100644 --- a/boards/arm/cxd56xx/spresense/src/cxd56_bringup.c +++ b/boards/arm/cxd56xx/spresense/src/cxd56_bringup.c @@ -267,6 +267,15 @@ int cxd56_bringup(void) scu_initialize(); #endif +#if defined(CONFIG_SENSORS_CXD5602PWBIMU) && \ + !defined(CONFIG_CXD56_CXD5602PWBIMU_LATE_INITIALIZE) + ret = board_cxd5602pwbimu_initialize(5); + if (ret < 0) + { + _err("ERROR: Failed to initialize CXD5602PWBIMU.\n"); + } +#endif + #ifdef CONFIG_CXD56_I2C_DRIVER #ifdef CONFIG_CXD56_I2C0 ret = board_i2cdev_initialize(0); diff --git a/drivers/sensors/CMakeLists.txt b/drivers/sensors/CMakeLists.txt index 8fcdd0e797a..bbc6b2758ce 100644 --- a/drivers/sensors/CMakeLists.txt +++ b/drivers/sensors/CMakeLists.txt @@ -450,5 +450,11 @@ if(CONFIG_SENSORS) list(APPEND SRCS hdc1008.c) endif() + # SONY CXD5602PWBIMU + + if(CONFIG_SENSORS_CXD5602PWBIMU) + list(APPEND SRCS cxd5602pwbimu.c) + endif() + target_sources(drivers PRIVATE ${SRCS}) endif() diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index 5139b988958..6d6675f3d91 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -432,6 +432,61 @@ config SENSORS_BMM150_THREAD_STACKSIZE endif # SENSORS_BMM150 +config SENSORS_CXD5602PWBIMU + bool "SONY CXD5602PWBIMU Inertial Measurement Sensor support" + default n + select SPI + select I2C + ---help--- + Enable driver support for the SONY CXD5602PWBIMU Inertial + Measurement sensor + +if SENSORS_CXD5602PWBIMU + +config SENSORS_CXD5602PWBIMU_NR_BUFFERS + int "Number of buffers to retrieve sensing data" + default 16 + +config SENSORS_CXD5602PWBIMU_OVERWRITE + bool "Overwrite circular buffer" + default y + +config SENSORS_CXD5602PWBIMU_NPOLLWAITERS + int "Number of waiters to poll" + default 1 + ---help--- + Number of waiters to poll + +config SENSORS_CXD5602PWBIMU_I2C_ADDRS_AUTO + bool "I2C address auto detection" + default y + ---help--- + Number of waiters to poll + +if !SENSORS_CXD5602PWBIMU_I2C_ADDRS_AUTO + +choice + prompt "I2C address selection" + default SENSORS_CXD5602PWBIMU_I2C_ADDRS_PRIMARY + +config SENSORS_CXD5602PWBIMU_I2C_ADDRS_PRIMARY + bool "Primary address series" + ---help--- + DipSW[2] -> off + Using address: 0x10, 0x11, 0x12, 0x13 + +config SENSORS_CXD5602PWBIMU_I2C_ADDRS_SECONDARY + bool "Secondary address series" + ---help--- + DipSW[2] -> on + Using address: 0x30, 0x31, 0x32, 0x33 + +endchoice + +endif # !SENSORS_CXD5602PWBIMU_I2C_ADDRS_AUTO + +endif # SENSORS_CXD5602PWBIMU + config SENSORS_BMP180 bool "Bosch BMP180 Barometer Sensor support" default n diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index f97cc2693d4..7a43756f664 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -450,6 +450,12 @@ ifeq ($(CONFIG_SENSORS_MAX31865),y) CSRCS += max31865.c endif +# SONY CXD5602PWBIMU + +ifeq ($(CONFIG_SENSORS_CXD5602PWBIMU),y) + CSRCS += cxd5602pwbimu.c +endif + # Include sensor driver build support DEPPATH += --dep-path sensors diff --git a/drivers/sensors/cxd5602pwbimu.c b/drivers/sensors/cxd5602pwbimu.c new file mode 100644 index 00000000000..1a597dbfdf8 --- /dev/null +++ b/drivers/sensors/cxd5602pwbimu.c @@ -0,0 +1,1815 @@ +/**************************************************************************** + * drivers/sensors/cxd5602pwbimu.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if defined(CONFIG_SENSORS_CXD5602PWBIMU) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Register Address */ + +/* Firmware update related registers */ + +#define CXD5602PWBIMU_INJECTION_READY (0x01) +#define CXD5602PWBIMU_UPDATE_RESULT (0x02) +#define CXD5602PWBIMU_INJECT_BINARY (0x03) +#define CXD5602PWBIMU_VERIFY_BINARY (0x04) +#define CXD5602PWBIMU_CHANGE_TO_UPDATEMODE (0x05) + +#define CXD5602PWBIMU_FW_VER (0x10) /* Firmware version */ +#define CXD5602PWBIMU_HW_REVISION (0x11) /* HW Revision */ +#define CXD5602PWBIMU_HW_UNIQUE_ID (0x12) /* HW UID */ +#define CXD5602PWBIMU_FSR (0x13) /* Full Scale */ +#define CXD5602PWBIMU_ODR (0x14) /* Output Data Rate */ +#define CXD5602PWBIMU_INTR_ENABLE (0x15) /* Interrupt enable */ +#define CXD5602PWBIMU_FIFO_THRESH (0x17) /* FIFO threshold */ +#define CXD5602PWBIMU_OUTPUT_ENABLE (0x18) /* Output Enable */ +#define CXD5602PWBIMU_USER_CALIB_COEF (0x19) /* User calibration */ +#define CXD5602PWBIMU_USER_CALIB_FLASH (0x1a) /* Flash user calib value */ + +#define CXD5602PWBIMU_MODE (0xff) /* Output Mode */ + +/* FSR */ + +#define FSR_ACCEL_2_G (0x00 << 4) /* Set ACCEL FullScale +/-2G */ +#define FSR_ACCEL_4_G (0x01 << 4) /* Set ACCEL FullScale +/-4G */ +#define FSR_ACCEL_8_G (0x02 << 4) /* Set ACCEL FullScale +/-8G */ +#define FSR_ACCEL_16_G (0x03 << 4) /* Set ACCEL FullScale +/-16G */ + +#define FSR_GYRO_125_DPS (0x00) /* Set GYRO FullScale +/-125dps */ +#define FSR_GYRO_250_DPS (0x01) /* Set GYRO FullScale +/-250dps */ +#define FSR_GYRO_500_DPS (0x02) /* Set GYRO FullScale +/-500dps */ +#define FSR_GYRO_1000_DPS (0x03) /* Set GYRO FullScale +/-1000dps */ +#define FSR_GYRO_2000_DPS (0x04) /* Set GYRO FullScale +/-2000dps */ +#define FSR_GYRO_4000_DPS (0x05) /* Set GYRO FullScale +/-4000dps */ + +/* ODR configuration */ + +#define ODR_15HZ (0x00) /* Set output data rate to 15Hz */ +#define ODR_30HZ (0x01) /* Set output data rate to 30Hz */ +#define ODR_60HZ (0x02) /* Set output data rate to 60Hz */ +#define ODR_120HZ (0x03) /* Set output data rate to 120Hz */ +#define ODR_240HZ (0x04) /* Set output data rate to 240Hz */ +#define ODR_480HZ (0x05) /* Set output data rate to 480Hz */ +#define ODR_960HZ (0x06) /* Set output data rate to 960Hz */ +#define ODR_1920HZ (0x07) /* Set output data rate to 1920Hz */ + +/* Output ENABLE */ + +#define OUTPUT_DISABLE (0x00) /* Disable 6axis data output */ +#define OUTPUT_ENABLE (0x01) /* Enable 6axis data output */ + +/* Update result status */ + +#define RESULT_NOEXEC (0x00) +#define RESULT_SUCCESS (0x01) +#define RESULT_FAILURE (0x02) + +/* I2C Clock Frequency */ + +#define I2C_CLK_FRERQ (400000) + +/* Default I2C Slave Addresses */ + +#define I2C_PRIMARY_ADDR0 (0x10) +#define I2C_PRIMARY_ADDR1 (0x11) +#define I2C_PRIMARY_ADDR2 (0x12) +#define I2C_PRIMARY_ADDR3 (0x13) +#define I2C_SECONDARY_ADDR0 (0x30) +#define I2C_SECONDARY_ADDR1 (0x31) +#define I2C_SECONDARY_ADDR2 (0x32) +#define I2C_SECONDARY_ADDR3 (0x33) + +#define I2C_ADDR_NOLOC (0xff) /* Not located */ + +#ifdef CONFIG_SENSORS_CXD5602PWBIMU_I2C_ADDRS_SECONDARY +#define I2C_SLAVE_ADDR0 I2C_SECONDARY_ADDR0 +#define I2C_SLAVE_ADDR1 I2C_SECONDARY_ADDR1 +#define I2C_SLAVE_ADDR2 I2C_SECONDARY_ADDR2 +#define I2C_SLAVE_ADDR3 I2C_SECONDARY_ADDR3 +#else +#define I2C_SLAVE_ADDR0 I2C_PRIMARY_ADDR0 +#define I2C_SLAVE_ADDR1 I2C_PRIMARY_ADDR1 +#define I2C_SLAVE_ADDR2 I2C_PRIMARY_ADDR2 +#define I2C_SLAVE_ADDR3 I2C_PRIMARY_ADDR3 +#endif + +/* Driver state */ + +#define STATE_INIT (0) +#define STATE_READY (1) +#define STATE_RUNNING (2) +#define STATE_UPDATE (3) + +#define NR_BUFFERS CONFIG_SENSORS_CXD5602PWBIMU_NR_BUFFERS +#define CIRCBUFSZ(priv) (NR_BUFFERS * (priv)->spi_xfersize) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct cxd5602pwbimu_dev_s +{ + FAR struct spi_dev_s *spi; /* SPI interface */ + uint32_t spi_xfersize; /* SPI TransferSize */ + + FAR struct i2c_master_s *i2c; /* I2C interface */ + uint8_t i2caddr[4]; /* I2C slave addresses */ + uint32_t i2cfreq; /* I2C clock frequency */ + int nslaves; /* Number of I2C slaves */ + + FAR cxd5602pwbimu_config_t *config; /* Board control interface */ + + mutex_t devlock; /* Device exclusion control */ + FAR struct pollfd *fds[CONFIG_SENSORS_CXD5602PWBIMU_NPOLLWAITERS]; + + sem_t dataready; /* for notify data ready */ + sem_t bufsem; /* lock for buffer is in use */ + struct circbuf_s buffer; /* Store sensing data */ + struct work_s work; /* Retrieve sensing data */ + int state; /* Driver state */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t cxd5602pwbimu_spigetreg8(FAR struct cxd5602pwbimu_dev_s *priv, + uint8_t regaddr); +static void cxd5602pwbimu_spiputreg8(FAR struct cxd5602pwbimu_dev_s *priv, + uint8_t regaddr, uint8_t regval); +static void cxd5602pwbimu_recv(FAR struct cxd5602pwbimu_dev_s *priv, + FAR uint8_t *buffer, int len); + +static int cxd5602pwbimu_getregsn(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, uint8_t regaddr, + FAR uint8_t *buffer, uint8_t len); +static int cxd5602pwbimu_getregsn_woadr(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, + FAR uint8_t *buffer, uint8_t len); +static int cxd5602pwbimu_putregsn(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, uint8_t regaddr, + FAR uint8_t *buffer, uint8_t len); +static int cxd5602pwbimu_putregs(FAR struct cxd5602pwbimu_dev_s *priv, + uint8_t regaddr, + FAR uint8_t *buffer, uint8_t len); +static int cxd5602pwbimu_putreg8(FAR struct cxd5602pwbimu_dev_s *priv, + uint8_t regaddr, uint8_t regval); +static int cxd5602pwbimu_putreg8n(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, + uint8_t regaddr, uint8_t regval); + +/* Device control methods */ + +static int cxd5602pwbimu_enable(FAR struct cxd5602pwbimu_dev_s *priv, + bool enable); +static int cxd5602pwbimu_setodr(FAR struct cxd5602pwbimu_dev_s *priv, + uint32_t rate); +static int cxd5602pwbimu_setcalib(FAR struct cxd5602pwbimu_dev_s *priv, + FAR cxd5602pwbimu_calib_t *calib); +static int cxd5602pwbimu_setdrange(FAR struct cxd5602pwbimu_dev_s *priv, + int accel, int gyro); + +/* Firmware update related functions */ + +static int cxd5602pwbimu_updatemode(FAR struct cxd5602pwbimu_dev_s *priv); +static int cxd5602pwbimu_waitforready(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid); +static int cxd5602pwbimu_sendfwchunk(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, FAR uint8_t *buf, int len); +static int cxd5602pwbimu_verifyfw(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid); +static int cxd5602pwbimu_updatefw(FAR struct cxd5602pwbimu_dev_s *priv, + FAR cxd5602pwbimu_updatefw_t *param); + +/* Character driver methods */ + +static int cxd5602pwbimu_open(FAR struct file *filep); +static int cxd5602pwbimu_close(FAR struct file *filep); +static ssize_t cxd5602pwbimu_read(FAR struct file *filep, FAR char *buffer, + size_t len); +static int cxd5602pwbimu_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); +static int cxd5602pwbimu_poll(FAR struct file *filep, + FAR struct pollfd *fds, bool setup); + +#ifdef CONFIG_SENSORS_CXD5602PWBIMU_I2C_ADDRS_AUTO +static int cxd5602pwbimu_detectaddrs(FAR struct cxd5602pwbimu_dev_s *priv); +#endif +static int cxd5602pwbimu_checkver(FAR struct cxd5602pwbimu_dev_s *priv); +static int cxd5602pwbimu_int_handler(int irq, FAR void *context, + FAR void *arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* This the vtable that supports the character driver interface */ + +static const struct file_operations g_cxd5602pwbimufops = +{ + cxd5602pwbimu_open, /* open */ + cxd5602pwbimu_close, /* close */ + cxd5602pwbimu_read, /* read */ + NULL, /* write */ + NULL, /* seek */ + cxd5602pwbimu_ioctl, /* ioctl */ + NULL, /* mmap */ + NULL, /* truncate */ + cxd5602pwbimu_poll /* poll */ +}; + +/**************************************************************************** + * Name: cxd5602pwbimu_configspi + * + * Description: + * + ****************************************************************************/ + +static inline void cxd5602pwbimu_configspi(FAR struct spi_dev_s *spi) +{ + /* Configure SPI for CXD5602PWBIMU */ + + SPI_SETMODE(spi, SPIDEV_MODE0); + SPI_SETBITS(spi, 8); + SPI_HWFEATURES(spi, 0); + SPI_SETFREQUENCY(spi, 1000000); +} + +/**************************************************************************** + * Name: cxd5602pwbimu_spigetreg8 + * + * Description: + * Read from an 8-bit CXD5602PWBIMU register + * + ****************************************************************************/ + +static uint8_t cxd5602pwbimu_spigetreg8(FAR struct cxd5602pwbimu_dev_s *priv, + uint8_t regaddr) +{ + FAR cxd5602pwbimu_config_t *config = priv->config; + uint8_t regval[2]; + + /* If SPI bus is shared then lock and configure it */ + + SPI_LOCK(priv->spi, true); + cxd5602pwbimu_configspi(priv->spi); + + /* Select the CXD5602PWBIMU */ + + /* put the code which control csx pin for CXD5602PWBIMU to low */ + + config->csx(config, false); + + SPI_SELECT(priv->spi, SPIDEV_IMU(0), true); + + /* Send register to read and get the next 2 bytes */ + + SPI_SEND(priv->spi, regaddr | 0x80); + SPI_RECVBLOCK(priv->spi, regval, 2); + + /* Deselect the CXD5602PWBIMU */ + + SPI_SELECT(priv->spi, SPIDEV_IMU(0), false); + + /* put the code which control csx pin for CXD5602PWBIMU to high */ + + config->csx(config, true); + + /* Unlock bus */ + + SPI_LOCK(priv->spi, false); + + /* The first byte has to be dropped */ + + return regval[1]; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_spiputreg8 + * + * Description: + * Write a value to an 8-bit CXD5602PWBIMU register + * + ****************************************************************************/ + +static void cxd5602pwbimu_spiputreg8(FAR struct cxd5602pwbimu_dev_s *priv, + uint8_t regaddr, uint8_t regval) +{ + FAR cxd5602pwbimu_config_t *config = priv->config; + + /* If SPI bus is shared then lock and configure it */ + + SPI_LOCK(priv->spi, true); + cxd5602pwbimu_configspi(priv->spi); + + /* Select the CXD5602PWBIMU */ + + /* put the code which control csx pin for CXD5602PWBIMU to low */ + + config->csx(config, false); + + SPI_SELECT(priv->spi, SPIDEV_IMU(0), true); + + /* Send register address and set the value */ + + SPI_SEND(priv->spi, regaddr); + SPI_SEND(priv->spi, regval); + + /* Deselect the CXD5602PWBIMU */ + + SPI_SELECT(priv->spi, SPIDEV_IMU(0), false); + + /* put the code which control csx pin for CXD5602PWBIMU to high */ + + config->csx(config, true); + + /* Unlock bus */ + + SPI_LOCK(priv->spi, false); + + up_mdelay(1); +} + +static void cxd5602pwbimu_recv(FAR struct cxd5602pwbimu_dev_s *priv, + FAR uint8_t *buffer, int len) +{ + FAR cxd5602pwbimu_config_t *config = priv->config; + + /* Clear receive buffer and set 1 to first byte MSB */ + + memset(buffer, 0, len); + buffer[0] = 0x80; + + /* If SPI bus is shared then lock and configure it */ + + SPI_LOCK(priv->spi, true); + cxd5602pwbimu_configspi(priv->spi); + + /* Select the CXD5602PWBIMU */ + + /* Put the code which control CSX pin for CXD5602PWBIMU to low */ + + config->csx(config, false); + + SPI_SELECT(priv->spi, SPIDEV_IMU(0), true); + + /* Exchange N bytes */ + + SPI_EXCHANGE(priv->spi, buffer, buffer, len); + + /* Deselect the CXD5602PWBIMU */ + + SPI_SELECT(priv->spi, SPIDEV_IMU(0), false); + + /* Put the code which control CSX pin for CXD5602PWBIMU to high */ + + config->csx(config, true); + + /* Unlock bus */ + + SPI_LOCK(priv->spi, false); +} + +/**************************************************************************** + * Name: cxd5602pwbimu_getregsn + * + * Description: + * Read value from CXD5602PWBIMU via I2C + * + ****************************************************************************/ + +static int cxd5602pwbimu_getregsn(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, uint8_t regaddr, + FAR uint8_t *buffer, uint8_t len) +{ + struct i2c_msg_s msg[2]; + int ret; + + msg[0].frequency = priv->i2cfreq; + msg[0].addr = priv->i2caddr[slaveid]; + msg[0].flags = I2C_M_NOSTOP; + msg[0].buffer = ®addr; + msg[0].length = 1; + + msg[1].frequency = priv->i2cfreq; + msg[1].addr = priv->i2caddr[slaveid]; + msg[1].flags = I2C_M_READ; + msg[1].buffer = buffer; + msg[1].length = len; + + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret < 0) + { + snerr("I2C_TRANSFER failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_getregsn_woadr + * + * Description: + * Read value from CXD5602PWBIMU via I2C with primary slave + * This function performs a read operation without a preceding + * write operation to the register address. + ****************************************************************************/ + +static int cxd5602pwbimu_getregsn_woadr(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, + FAR uint8_t *buffer, uint8_t len) +{ + struct i2c_msg_s msg; + int ret; + + msg.frequency = priv->i2cfreq; + msg.addr = priv->i2caddr[slaveid]; + msg.flags = I2C_M_READ; + msg.buffer = buffer; + msg.length = len; + + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + if (ret < 0) + { + snerr("I2C_TRANSFER failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_putregsn + * + * Description: + * Write value to CXD5602PWBIMU via i2c. + * + ****************************************************************************/ + +static int cxd5602pwbimu_putregsn(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, uint8_t regaddr, + FAR uint8_t *buffer, uint8_t len) +{ + struct i2c_msg_s msg[2]; + int ret; + + msg[0].frequency = priv->i2cfreq; + msg[0].addr = priv->i2caddr[slaveid]; + msg[0].flags = I2C_M_NOSTOP; + msg[0].buffer = ®addr; + msg[0].length = 1; + + msg[1].frequency = priv->i2cfreq; + msg[1].addr = priv->i2caddr[slaveid]; + msg[1].flags = 0; + msg[1].buffer = buffer; + msg[1].length = len; + + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret < 0) + { + snerr("I2C_TRANSFER failed: %d\n", ret); + } + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_putregs + * + * Description: + * Write value to CXD5602PWBIMU via i2c with primary slave. + * + ****************************************************************************/ + +static int cxd5602pwbimu_putregs(FAR struct cxd5602pwbimu_dev_s *priv, + uint8_t regaddr, + FAR uint8_t *buffer, uint8_t len) +{ + return cxd5602pwbimu_putregsn(priv, 0, regaddr, buffer, len); +} + +/**************************************************************************** + * Name: cxd5602pwbimu_putreg8 + * + * Description: + * Write value to CXD5602PWBIMU via i2c with primary slave. + * + ****************************************************************************/ + +static int cxd5602pwbimu_putreg8(FAR struct cxd5602pwbimu_dev_s *priv, + uint8_t regaddr, uint8_t regval) +{ + return cxd5602pwbimu_putregsn(priv, 0, regaddr, ®val, 1); +} + +/**************************************************************************** + * Name: cxd5602pwbimu_putreg8n + * + * Description: + * Write value to CXD5602PWBIMU via i2c with specified slave address. + * + ****************************************************************************/ + +static int cxd5602pwbimu_putreg8n(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, + uint8_t regaddr, uint8_t regval) +{ + return cxd5602pwbimu_putregsn(priv, slaveid, regaddr, ®val, 1); +} + +#ifdef CONFIG_SENSORS_CXD5602PWBIMU_I2C_ADDRS_AUTO +static int cxd5602pwbimu_detectaddrs(FAR struct cxd5602pwbimu_dev_s *priv) +{ + uint8_t val; + int ret; + + if (priv->nslaves) + { + return 0; + } + + /* First try to get register from primary PSoC. + * If an address is determined to be either primary or secondary, the + * other addresses are set in the same series for now. + */ + + priv->i2caddr[0] = I2C_PRIMARY_ADDR0; + ret = cxd5602pwbimu_getregsn(priv, 0, CXD5602PWBIMU_FW_VER, &val, 1); + if (ret) + { + priv->i2caddr[0] = I2C_SECONDARY_ADDR0; + ret = cxd5602pwbimu_getregsn(priv, 0, CXD5602PWBIMU_FW_VER, &val, 1); + if (ret) + { + /* If no response from primary and secondary address, the device + * not found. + */ + + return -ENODEV; + } + + priv->i2caddr[1] = I2C_SECONDARY_ADDR1; + priv->i2caddr[2] = I2C_SECONDARY_ADDR2; + priv->i2caddr[3] = I2C_SECONDARY_ADDR3; + } + else + { + priv->i2caddr[1] = I2C_PRIMARY_ADDR1; + priv->i2caddr[2] = I2C_PRIMARY_ADDR2; + priv->i2caddr[3] = I2C_PRIMARY_ADDR3; + } + + sninfo("Detected address 0x%02x\n", priv->i2caddr[0]); + + return 0; +} +#endif + +static int cxd5602pwbimu_checkaddrs(FAR struct cxd5602pwbimu_dev_s *priv) +{ + int i; + uint8_t val; + int nslaves; + int ret; + + /* Counting available PSoCs */ + + nslaves = 0; + for (i = 0; i < 4; i++) + { + ret = cxd5602pwbimu_getregsn(priv, i, CXD5602PWBIMU_FW_VER, &val, 1); + if (ret) + { + priv->i2caddr[i] = I2C_ADDR_NOLOC; + } + else + { + nslaves++; + } + } + + if (nslaves == 0) + { + return -ENODEV; + } + + priv->nslaves = nslaves; + + /* Check that the address dip switches are valid. */ + + for (i = 2; i < 4; i++) + { + if (priv->i2caddr[i] == I2C_ADDR_NOLOC) + { + priv->i2caddr[i] = ((priv->i2caddr[i - 2] & 0x30) ^ 0x20) + i; + ret = cxd5602pwbimu_getregsn(priv, i, CXD5602PWBIMU_FW_VER, + &val, 1); + priv->i2caddr[i] = I2C_ADDR_NOLOC; + if (ret == 0) + { + snerr("Dip switch setting mismatch detected.\n"); + return -EFAULT; + } + } + } + + sninfo("%d slaves are detected.\n", priv->nslaves); + + return 0; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_checkver + * + * Description: + * Read and verify the CXD5602PWBIMU firmware versions + * + ****************************************************************************/ + +static int cxd5602pwbimu_checkver(FAR struct cxd5602pwbimu_dev_s *priv) +{ + uint8_t ver[4]; + uint8_t val = 0; + int ret; + int i; + + ret = cxd5602pwbimu_getregsn(priv, 0, CXD5602PWBIMU_MODE, &val, 1); + if (ret != OK) + { + return -1; + } + + if (val == 0) + { + /* Now in update mode */ + + return 1; + } + + /* Read and verify 4 firmware versions. + * + * 1 add-on board has 2 chips, and this add-on board can stack up to + * 2 add-on boards, so maximum chips are 4. + * They must have the same firmware version. + */ + + memset(ver, 0, sizeof(ver)); + for (i = 0; i < priv->nslaves; i++) + { + ret = cxd5602pwbimu_getregsn(priv, i, CXD5602PWBIMU_FW_VER, &val, 1); + if (ret == 0) + { + ver[i] = val; + sninfo("[%d] Version: %02x\n", i, val); + } + } + + if (ver[0] == 0) + { + /* If primary chip not found, the board not connected. */ + + return -1; + } + + if (ver[0] != ver[1]) + { + /* Primary and secondary firmwares are not matched, need to update */ + + return 1; + } + + if (priv->nslaves > 2) + { + if (ver[0] != ver[2] || ver[0] != ver[3]) + { + /* 2nd board firmware does not matched with primary firmware, + * need to update. + */ + + return 1; + } + } + + return 0; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_enable + * + * Description: + * Start sensing + * + ****************************************************************************/ + +static int cxd5602pwbimu_enable(FAR struct cxd5602pwbimu_dev_s *priv, + bool enable) +{ + int ret = OK; + + if (priv->state == STATE_UPDATE) + { + return -EBUSY; + } + + if (enable) + { + if (priv->state == STATE_RUNNING) + { + return OK; /* Already running, ignore. */ + } + + ret = cxd5602pwbimu_putreg8(priv, CXD5602PWBIMU_OUTPUT_ENABLE, + OUTPUT_ENABLE); + if (!ret) + { + priv->state = STATE_RUNNING; + } + } + else + { + if (priv->state == STATE_READY) + { + return OK; /* Already stopped, ignore. */ + } + + cxd5602pwbimu_spiputreg8(priv, CXD5602PWBIMU_OUTPUT_ENABLE, + OUTPUT_DISABLE); + priv->state = STATE_READY; + } + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_setodr + * + * Description: + * Set sampling rate + * + ****************************************************************************/ + +static int cxd5602pwbimu_setodr(FAR struct cxd5602pwbimu_dev_s *priv, + uint32_t rate) +{ + uint8_t val; + + if (priv->state != STATE_READY) + { + return -EBUSY; + } + + switch (rate) + { + case 15: + val = ODR_15HZ; + break; + + case 30: + val = ODR_30HZ; + break; + + case 60: + val = ODR_60HZ; + break; + + case 120: + val = ODR_120HZ; + break; + + case 240: + val = ODR_240HZ; + break; + + case 480: + val = ODR_480HZ; + break; + + case 960: + val = ODR_960HZ; + break; + + case 1920: + val = ODR_1920HZ; + break; + + default: + return -EINVAL; + } + + return cxd5602pwbimu_putreg8(priv, CXD5602PWBIMU_ODR, val); +} + +/**************************************************************************** + * Name: cxd5602pwbimu_setcalib + * + * Description: + * Set calibration + * + ****************************************************************************/ + +static int cxd5602pwbimu_setcalib(FAR struct cxd5602pwbimu_dev_s *priv, + FAR cxd5602pwbimu_calib_t *calib) +{ + int ret; + + if (priv->state != STATE_READY) + { + return -EBUSY; + } + + ret = cxd5602pwbimu_putregs(priv, CXD5602PWBIMU_USER_CALIB_COEF, + (FAR uint8_t *)calib, + sizeof(cxd5602pwbimu_calib_t)); + if (ret == 0) + { + ret = cxd5602pwbimu_putreg8(priv, CXD5602PWBIMU_USER_CALIB_FLASH, 1); + } + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_setdrange + * + * Description: + * Set dynamic range setting for accelerometer and gyro. + * + ****************************************************************************/ + +static int cxd5602pwbimu_setdrange(FAR struct cxd5602pwbimu_dev_s *priv, + int accel, int gyro) +{ + int val; + + if (priv->state != STATE_READY) + { + return -EBUSY; + } + + switch (accel) + { + case 2: + val = FSR_ACCEL_2_G; + break; + + case 4: + val = FSR_ACCEL_4_G; + break; + + case 8: + val = FSR_ACCEL_8_G; + break; + + case 16: + val = FSR_ACCEL_16_G; + break; + + default: + return -EINVAL; + } + + switch (gyro) + { + case 125: + val |= FSR_GYRO_125_DPS; + break; + + case 250: + val |= FSR_GYRO_250_DPS; + break; + + case 500: + val |= FSR_GYRO_500_DPS; + break; + + case 1000: + val |= FSR_GYRO_1000_DPS; + break; + + case 2000: + val |= FSR_GYRO_2000_DPS; + break; + + case 4000: + val |= FSR_GYRO_4000_DPS; + break; + + default: + return -EINVAL; + } + + return cxd5602pwbimu_putreg8(priv, CXD5602PWBIMU_FSR, val); +} + +/**************************************************************************** + * Name: cxd5602pwbimu_setfifothresh + * + * Description: + * Set FIFO threshold. Driver resize the circbuf by configured + * threshold. + * + ****************************************************************************/ + +static int cxd5602pwbimu_setfifothresh(FAR struct cxd5602pwbimu_dev_s *priv, + int thresh) +{ + size_t size; + int ret; + + if (priv->state != STATE_READY) + { + return -EBUSY; + } + + if (thresh < 1 || thresh > 4) + { + return -EINVAL; + } + + ret = cxd5602pwbimu_putreg8(priv, CXD5602PWBIMU_FIFO_THRESH, thresh); + if (!ret) + { + priv->spi_xfersize = sizeof(cxd5602pwbimu_data_t) * thresh; + size = (NR_BUFFERS / thresh) * thresh * sizeof(cxd5602pwbimu_data_t); + sninfo("Resize circbuf in %d bytes\n", size); + ret = circbuf_resize(&priv->buffer, size); + } + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_updatemode + * + * Description: + * Change CXD5602PWBIMU mode to update mode + * + ****************************************************************************/ + +static int cxd5602pwbimu_updatemode(FAR struct cxd5602pwbimu_dev_s *priv) +{ + int i; + int ret; + uint8_t val; + + ret = cxd5602pwbimu_getregsn(priv, 0, CXD5602PWBIMU_MODE, &val, 1); + if (ret) + { + return ret; + } + + if (val == 0) + { + /* Already in update mode */ + + return 0; + } + + for (i = 0; i < priv->nslaves; i++) + { + ret = cxd5602pwbimu_putreg8n(priv, i, + CXD5602PWBIMU_CHANGE_TO_UPDATEMODE, 1); + if (ret) + { + return ret; + } + } + + up_mdelay(100); + + return 0; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_waitforready + * + * Description: + * Wait for ready to updater can take the firmware binary data. + * + ****************************************************************************/ + +static int cxd5602pwbimu_waitforready(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid) +{ + int retry; + uint8_t val; + int ret; + + val = 0; + retry = 1000; + do + { + ret = cxd5602pwbimu_getregsn(priv, slaveid, + CXD5602PWBIMU_INJECTION_READY, + &val, 1); + if (ret) + { + return ret; + } + } + while (val == 0 && --retry); + + return val == 1 ? OK : -ETIMEDOUT; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_sendfwchunk + * + * Description: + * Send chunk of CXD5602PWBIMU Add-on board firmware. + * + ****************************************************************************/ + +static int cxd5602pwbimu_sendfwchunk(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid, FAR uint8_t *buf, int len) +{ + int ret; + + ret = cxd5602pwbimu_waitforready(priv, slaveid); + if (ret) + { + return ret; + } + + ret = cxd5602pwbimu_putregsn(priv, slaveid, CXD5602PWBIMU_INJECT_BINARY, + buf, len); + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_verifyfw + * + * Description: + * Verify updated CXD5602PWBIMU Add-on board firmwares. + * + ****************************************************************************/ + +static int cxd5602pwbimu_verifyfw(FAR struct cxd5602pwbimu_dev_s *priv, + int slaveid) +{ + int retry; + uint8_t val; + int ret; + + ret = cxd5602pwbimu_waitforready(priv, slaveid); + if (ret) + { + return ret; + } + + val = 1; + ret = cxd5602pwbimu_putregsn(priv, slaveid, CXD5602PWBIMU_VERIFY_BINARY, + &val, 1); + if (ret) + { + return ret; + } + + val = 0; + retry = 1000; + do + { + ret = cxd5602pwbimu_getregsn(priv, slaveid, + CXD5602PWBIMU_UPDATE_RESULT, + &val, 1); + if (ret) + { + return ret; + } + } + while (val == RESULT_NOEXEC && --retry); + + if (val == RESULT_SUCCESS) + { + ret = OK; + } + else if (val == RESULT_FAILURE) + { + ret = -EFAULT; + } + else + { + ret = -ETIMEDOUT; + } + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_updatefw + * + * Description: + * Update CXD5602PWBIMU Add-on board firmwares. + * + ****************************************************************************/ + +static int cxd5602pwbimu_updatefw(FAR struct cxd5602pwbimu_dev_s *priv, + FAR cxd5602pwbimu_updatefw_t *param) +{ + struct file finfo; + struct stat stat; + FAR uint8_t *buf; + off_t size; + off_t total; + int i; + int len; + int ret; + + priv->state = STATE_UPDATE; + + buf = (FAR uint8_t *)kmm_malloc(128); + if (!buf) + { + return -ENOMEM; + } + + ret = file_open(&finfo, param->path, O_RDONLY | O_CLOEXEC); + if (ret < 0) + { + kmm_free(buf); + return ret; + } + + ret = file_fstat(&finfo, &stat); + if (ret < 0) + { + goto errout; + } + + total = stat.st_size; + + sninfo("Change update mode\n"); + ret = cxd5602pwbimu_updatemode(priv); + if (ret) + { + goto errout; + } + + size = 0; + sninfo("Updating"); + for (; ; ) + { + ret = file_read(&finfo, buf, 128); + if (ret <= 0) + { + /* break loop when error or EOF */ + + break; + } + + len = ret; + + for (i = 0; i < priv->nslaves; i++) + { + sninfo("Send %d bytes to %d ", len, i); + ret = cxd5602pwbimu_sendfwchunk(priv, i, buf, len); + if (ret) + { + goto errout; + } + + sninfo("OK\n"); + } + + /* Inform current progress to caller */ + + size += len; + if (param->progress) + { + param->progress(size, total); + } + } + + sninfo("Verifying firmwares"); + + for (i = 0; i < priv->nslaves; i++) + { + ret = cxd5602pwbimu_verifyfw(priv, i); + if (ret) + { + break; + } + } + + sninfo("OK\n"); + +errout: + kmm_free(buf); + file_close(&finfo); + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_open + * + * Description: + * Standard character driver open method. + * + ****************************************************************************/ + +static int cxd5602pwbimu_open(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct cxd5602pwbimu_dev_s *priv = inode->i_private; + FAR cxd5602pwbimu_config_t *config = priv->config; + int ret; + + /* Power on and reset device */ + + config->power(config, true); + config->reset(config, true); + up_udelay(20); + config->reset(config, false); + up_mdelay(150); + +#ifdef CONFIG_SENSORS_CXD5602PWBIMU_I2C_ADDRS_AUTO + ret = cxd5602pwbimu_detectaddrs(priv); + if (ret < 0) + { + return ret; + } +#endif + + ret = cxd5602pwbimu_checkaddrs(priv); + if (ret < 0) + { + return ret; + } + + ret = cxd5602pwbimu_checkver(priv); + if (ret < 0) + { + return -ENODEV; + } + + if (ret > 0) + { + /* If return value is positive, firmware mismatch has been detected. + * The driver can be used for only updating firmwares. + * Additionally, change each PSoCs to update mode for indicate on board + * LEDs to in update mode. + */ + + cxd5602pwbimu_updatemode(priv); + priv->state = STATE_UPDATE; + + return OK; + } + + circbuf_init(&priv->buffer, NULL, CIRCBUFSZ(priv)); + + /* Enable data ready interrupt */ + + cxd5602pwbimu_putreg8(priv, CXD5602PWBIMU_INTR_ENABLE, 1); + config->irq_attach(config, cxd5602pwbimu_int_handler, priv); + config->irq_enable(config, true); + + priv->state = STATE_READY; + + return OK; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_close + * + * Description: + * Standard character driver close method. + * + ****************************************************************************/ + +static int cxd5602pwbimu_close(FAR struct file *filep) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct cxd5602pwbimu_dev_s *priv = inode->i_private; + FAR cxd5602pwbimu_config_t *config = priv->config; + + /* Stop output 6axis data and power down */ + + config->irq_enable(config, false); + cxd5602pwbimu_enable(priv, false); + + up_mdelay(100); + + config->reset(config, true); + + if (circbuf_is_init(&priv->buffer)) + { + circbuf_uninit(&priv->buffer); + } + + priv->state = STATE_INIT; + + return OK; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_read + * + * Description: + * Standard character driver read method. + * + ****************************************************************************/ + +static ssize_t cxd5602pwbimu_read(FAR struct file *filep, FAR char *buffer, + size_t len) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct cxd5602pwbimu_dev_s *priv = inode->i_private; + int ret; + + ret = nxmutex_lock(&priv->devlock); + if (ret < 0) + { + return ret; + } + + if (circbuf_is_empty(&priv->buffer)) + { + if (filep->f_oflags & O_NONBLOCK) + { + nxmutex_unlock(&priv->devlock); + return -EAGAIN; + } + + nxmutex_unlock(&priv->devlock); + ret = nxsem_wait_uninterruptible(&priv->dataready); + if (ret) + { + return ret; + } + + nxmutex_lock(&priv->devlock); + } + + ret = nxsem_wait_uninterruptible(&priv->bufsem); + if (ret) + { + nxmutex_unlock(&priv->devlock); + return ret; + } + + ret = circbuf_read(&priv->buffer, buffer, len); + nxsem_post(&priv->bufsem); + + nxmutex_unlock(&priv->devlock); + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_ioctl + * + * Description: + * Standard character driver ioctl method. + * + ****************************************************************************/ + +static int cxd5602pwbimu_ioctl(FAR struct file *filep, int cmd, + unsigned long arg) +{ + FAR struct inode *inode = filep->f_inode; + FAR struct cxd5602pwbimu_dev_s *priv = inode->i_private; + int ret = OK; + + ret = nxmutex_lock(&priv->devlock); + if (ret < 0) + { + return ret; + } + + if (priv->state == STATE_RUNNING && cmd != SNIOC_ENABLE) + { + nxmutex_unlock(&priv->devlock); + return -EBUSY; + } + + switch (cmd) + { + case SNIOC_SETDATASIZE: + if (priv->state != STATE_READY) + { + ret = -EBUSY; + } + else + { + priv->spi_xfersize = arg; + circbuf_resize(&priv->buffer, CIRCBUFSZ(priv)); + } + break; + + case SNIOC_SSAMPRATE: + { + uint32_t rate = (uint32_t)arg; + + ret = cxd5602pwbimu_setodr(priv, rate); + } + break; + + case SNIOC_SDRANGE: + { + FAR cxd5602pwbimu_range_t *r = (FAR cxd5602pwbimu_range_t *)arg; + + ret = cxd5602pwbimu_setdrange(priv, r->accel, r->gyro); + } + break; + + case SNIOC_SCALIB: + { + FAR cxd5602pwbimu_calib_t *c = (FAR cxd5602pwbimu_calib_t *)arg; + + ret = cxd5602pwbimu_setcalib(priv, c); + } + break; + + case SNIOC_ENABLE: + ret = cxd5602pwbimu_enable(priv, arg == 1); + break; + + case SNIOC_SFIFOTHRESH: + ret = cxd5602pwbimu_setfifothresh(priv, arg); + break; + + case SNIOC_UPDATEFW: + { + FAR cxd5602pwbimu_updatefw_t *p = + (FAR cxd5602pwbimu_updatefw_t *)arg; + + ret = cxd5602pwbimu_updatefw(priv, p); + } + break; + + case SNIOC_WREGSPI: + { + FAR cxd5602pwbimu_regs_t *p = (FAR cxd5602pwbimu_regs_t *)arg; + + cxd5602pwbimu_spiputreg8(priv, p->addr, *p->value); + } + break; + + case SNIOC_RREGSPI: + { + FAR cxd5602pwbimu_regs_t *p = (FAR cxd5602pwbimu_regs_t *)arg; + + p->value[0] = cxd5602pwbimu_spigetreg8(priv, p->addr); + } + break; + + case SNIOC_WREGS: + { + FAR cxd5602pwbimu_regs_t *p = (FAR cxd5602pwbimu_regs_t *)arg; + + ret = cxd5602pwbimu_putregsn(priv, p->slaveid, + p->addr, p->value, p->len); + } + break; + + case SNIOC_RREGS: + { + FAR cxd5602pwbimu_regs_t *p = (FAR cxd5602pwbimu_regs_t *)arg; + + ret = cxd5602pwbimu_getregsn(priv, p->slaveid, + p->addr, p->value, p->len); + } + break; + + case SNIOC_RREGS_WOADR: + { + FAR cxd5602pwbimu_regs_t *p = (FAR cxd5602pwbimu_regs_t *)arg; + + ret = cxd5602pwbimu_getregsn_woadr(priv, p->slaveid, + p->value, p->len); + } + break; + + case SNIOC_GETBNUM: + { + ret = priv->nslaves == 2 ? 1 : + priv->nslaves == 4 ? 2 : 0; + } + break; + + default: + snerr("Unrecognized cmd: %d\n", cmd); + ret = -ENOTTY; + break; + } + + nxmutex_unlock(&priv->devlock); + + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_poll + * + * Description: + * Polling method for CXD5602PWBIMU data ready + * + ****************************************************************************/ + +static int cxd5602pwbimu_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inode *inode; + FAR struct cxd5602pwbimu_dev_s *priv; + uint32_t flags; + int ret = OK; + int i; + + DEBUGASSERT(fds); + inode = filep->f_inode; + + DEBUGASSERT(inode->i_private); + priv = inode->i_private; + + /* Get exclusive access */ + + ret = nxmutex_lock(&priv->devlock); + if (ret < 0) + { + return ret; + } + + if (setup) + { + /* Ignore waits that do not include POLLIN */ + + if ((fds->events & POLLIN) == 0) + { + ret = -EDEADLK; + goto out; + } + + /* This is a request to set up the poll. Find an available slot for + * the poll structure reference. + */ + + for (i = 0; i < CONFIG_SENSORS_CXD5602PWBIMU_NPOLLWAITERS; i++) + { + /* Find an available slot */ + + if (!priv->fds[i]) + { + /* Bind the poll structure and this slot */ + + priv->fds[i] = fds; + fds->priv = &priv->fds[i]; + break; + } + } + + if (i >= CONFIG_SENSORS_CXD5602PWBIMU_NPOLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto out; + } + + flags = enter_critical_section(); + if (!circbuf_is_empty(&priv->buffer)) + { + poll_notify(priv->fds, + CONFIG_SENSORS_CXD5602PWBIMU_NPOLLWAITERS, POLLIN); + } + + leave_critical_section(flags); + } + else if (fds->priv) + { + /* This is a request to tear down the poll. */ + + struct pollfd **slot = (struct pollfd **)fds->priv; + DEBUGASSERT(slot != NULL); + + /* Remove all memory of the poll setup */ + + *slot = NULL; + fds->priv = NULL; + } + +out: + nxmutex_unlock(&priv->devlock); + return ret; +} + +/**************************************************************************** + * Name: cxd5602pwbimu_worker + * + * Description: + * Work queue function for retrieve sensing data. + * + ****************************************************************************/ + +static void cxd5602pwbimu_worker(FAR void *arg) +{ + FAR struct cxd5602pwbimu_dev_s *priv = + (FAR struct cxd5602pwbimu_dev_s *)arg; + FAR cxd5602pwbimu_config_t *config = priv->config; + FAR void *ptr; + size_t size; + int ret; + + /* 500us is the maximum sampling rate */ + + ret = nxsem_tickwait_uninterruptible(&priv->bufsem, USEC2TICK(500)); + if (ret) + { + snerr("ERROR: Data buffer is locked too long time.\n"); + return; + } + + /* Receive 1 sensing data. + * If two or more data are ready, re-enter this routine after interrupt + * enable, and continue this sequence until sensing data on the device + * is empty. + */ + +#ifndef CONFIG_SENSORS_CXD5602PWBIMU_OVERWRITE + if (circbuf_is_full(&priv->buffer)) + { + cxd5602pwbimu_data_t data; + + /* Drain sensor data but not copy into circular buffer */ + + cxd5602pwbimu_recv(priv, (FAR uint8_t *)&data, priv->spi_xfersize); + } + else +#endif + { +#ifdef CONFIG_SENSORS_CXD5602PWBIMU_OVERWRITE + if (circbuf_is_full(&priv->buffer)) + { + /* Advance the read pointer by the transfer size. + * We need to do it for overwrite feature in circbuf. + */ + + circbuf_readcommit(&priv->buffer, priv->spi_xfersize); + } +#endif + + ptr = circbuf_get_writeptr(&priv->buffer, &size); + cxd5602pwbimu_recv(priv, ptr, priv->spi_xfersize); + circbuf_writecommit(&priv->buffer, priv->spi_xfersize); + } + + nxsem_post(&priv->bufsem); + + config->irq_enable(config, true); + + /* Notify data get ready to user */ + + nxsem_post(&priv->dataready); + poll_notify(priv->fds, CONFIG_SENSORS_CXD5602PWBIMU_NPOLLWAITERS, POLLIN); +} + +/**************************************************************************** + * Name: cxd5602pwbimu_int_handler + * + * Description: + * Interrupt handler for CXD5602PWBIMU data ready + * + ****************************************************************************/ + +static int cxd5602pwbimu_int_handler(int irq, FAR void *context, + FAR void *arg) +{ + FAR struct cxd5602pwbimu_dev_s *priv = + (FAR struct cxd5602pwbimu_dev_s *)arg; + FAR cxd5602pwbimu_config_t *config = priv->config; + int ret; + + config->irq_enable(config, false); + + if (work_available(&priv->work)) + { + ret = work_queue(HPWORK, &priv->work, cxd5602pwbimu_worker, priv, 0); + if (ret < 0) + { + snerr("ERROR: Failed to queue work: %d\n", ret); + return ret; + } + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: cxd5602pwbimu_register + * + * Description: + * Register the CXD5602PWBIMU character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/imu0" + * dev_spi - An instance of the SPI interface to use to communicate + * with CXD5602PWBIMU + * dev_i2c - An instance of the I2C interface to use to communicate + * with CXD5602PWBIMU + * config - An instance of the interrupt configuration data structure + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int cxd5602pwbimu_register(FAR const char *devpath, + FAR struct spi_dev_s *dev_spi, + FAR struct i2c_master_s *dev_i2c, + FAR cxd5602pwbimu_config_t *config) +{ + FAR struct cxd5602pwbimu_dev_s *priv; + size_t size = sizeof(struct cxd5602pwbimu_dev_s); + int ret; + + priv = (FAR struct cxd5602pwbimu_dev_s *)kmm_zalloc(size); + if (!priv) + { + snerr("Failed to allocate instance\n"); + return -ENOMEM; + } + + priv->spi = dev_spi; + priv->spi_xfersize = sizeof(cxd5602pwbimu_data_t); + + priv->i2c = dev_i2c; + priv->i2cfreq = I2C_CLK_FRERQ; + priv->i2caddr[0] = I2C_SLAVE_ADDR0; + priv->i2caddr[1] = I2C_SLAVE_ADDR1; + priv->i2caddr[2] = I2C_SLAVE_ADDR2; + priv->i2caddr[3] = I2C_SLAVE_ADDR3; + priv->nslaves = 0; + priv->state = STATE_INIT; + + priv->config = config; + nxmutex_init(&priv->devlock); + + nxsem_init(&priv->dataready, 0, 0); + nxsem_init(&priv->bufsem, 0, 1); + + ret = register_driver(devpath, &g_cxd5602pwbimufops, 0666, priv); + if (ret < 0) + { + snerr("Failed to register driver: %d\n", ret); + kmm_free(priv); + return ret; + } + + sninfo("CXD5602PWBIMU driver loaded successfully!\n"); + return OK; +} + +#endif /* CONFIG_SENSORS_CXD5602PWBIMU */ diff --git a/include/nuttx/sensors/cxd5602pwbimu.h b/include/nuttx/sensors/cxd5602pwbimu.h new file mode 100644 index 00000000000..78463bfa820 --- /dev/null +++ b/include/nuttx/sensors/cxd5602pwbimu.h @@ -0,0 +1,172 @@ +/**************************************************************************** + * include/nuttx/sensors/cxd5602pwbimu.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_SENSORS_CXD5602PWBIMU_H +#define __INCLUDE_NUTTX_SENSORS_CXD5602PWBIMU_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include + +#if defined(CONFIG_SENSORS_CXD5602PWBIMU) + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* IOCTL Commands ***********************************************************/ + +#define SNIOC_ENABLE _SNIOC(0x0001) +#define SNIOC_SSAMPRATE _SNIOC(0x0002) +#define SNIOC_SDRANGE _SNIOC(0x0003) +#define SNIOC_SCALIB _SNIOC(0x0004) +#define SNIOC_SFIFOTHRESH _SNIOC(0x0005) +#define SNIOC_UPDATEFW _SNIOC(0x0010) + +#define SNIOC_SETDATASIZE _SNIOC(0x0080) +#define SNIOC_WREGSPI _SNIOC(0x0081) +#define SNIOC_RREGSPI _SNIOC(0x0082) +#define SNIOC_WREGS _SNIOC(0x0083) +#define SNIOC_RREGS _SNIOC(0x0084) +#define SNIOC_RREGS_WOADR _SNIOC(0x0085) +#define SNIOC_GETBNUM _SNIOC(0x0086) + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* Interrupt configuration data structure */ + +typedef struct cxd5602pwbimu_config_s +{ + CODE int (*irq_attach)(FAR const struct cxd5602pwbimu_config_s *state, + xcpt_t isr, FAR void *arg); + CODE void (*irq_enable)(FAR const struct cxd5602pwbimu_config_s *state, + bool enable); + CODE void (*csx)(FAR const struct cxd5602pwbimu_config_s *state, + bool pol); + CODE void (*power)(FAR const struct cxd5602pwbimu_config_s *state, + bool pol); + CODE void (*reset)(FAR const struct cxd5602pwbimu_config_s *state, + bool assert); +} cxd5602pwbimu_config_t; + +/**************************************************************************** + * struct 6-axis data + ****************************************************************************/ + +struct cxd5602pwbimu_data_s +{ + uint32_t timestamp; /* timestamp */ + float temp; /* temperature */ + float gx; /* gyro x */ + float gy; /* gyro y */ + float gz; /* gyro z */ + float ax; /* accel x */ + float ay; /* accel y */ + float az; /* accel z */ +}; +typedef struct cxd5602pwbimu_data_s cxd5602pwbimu_data_t; + +struct cxd5602pwbimu_range_s +{ + int accel; /* 2, 4, 8, 16 */ + int gyro; /* 125, 250, 500, 1000, 2000, 4000 */ +}; +typedef struct cxd5602pwbimu_range_s cxd5602pwbimu_range_t; + +begin_packed_struct struct cxd5602pwbimu_calib_s +{ + uint8_t offset; + uint32_t coef; +} end_packed_struct; +typedef struct cxd5602pwbimu_calib_s cxd5602pwbimu_calib_t; + +struct cxd5602pwbimu_regs_s +{ + uint8_t addr; /* Register address */ + FAR uint8_t *value; /* Write value or read value */ + uint8_t len; /* Length of value */ + int slaveid; /* Target 0=master, 1,2,3=slave{1,2,3} */ +}; +typedef struct cxd5602pwbimu_regs_s cxd5602pwbimu_regs_t; + +struct cxd5602pwbimu_updatefw_s +{ + FAR const char *path; + void (*progress)(off_t current, off_t total); +}; +typedef struct cxd5602pwbimu_updatefw_s cxd5602pwbimu_updatefw_t; + +struct spi_dev_s; +struct i2c_master_s; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Name: cxd5602pwbimu_register + * + * Description: + * Register the CXD5602PWBIMU character device as 'devpath' + * + * Input Parameters: + * devpath - The full path to the driver to register. E.g., "/dev/imu0" + * dev_spi - An instance of the SPI interface to use to communicate + * with CXD5602PWBIMU + * dev_i2c - An instance of the I2C interface to use to communicate + * with CXD5602PWBIMU + * config - An instance of the interrupt configuration data structure + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int cxd5602pwbimu_register(FAR const char *devpath, + FAR struct spi_dev_s *dev_spi, + FAR struct i2c_master_s *dev_i2c, + FAR cxd5602pwbimu_config_t *config); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_SENSORS_CXD5602PWBIMU */ +#endif /* __INCLUDE_NUTTX_SENSORS_CXD5602PWBIMU_H */