This is an automated email from the ASF dual-hosted git repository.

simbit18 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git

commit d66aca54585aedb7f8dc7ef291b5d1176081ecd2
Author: Alan Carvalho de Assis <[email protected]>
AuthorDate: Fri Oct 9 11:38:28 2026 -0300

    drivers/sensors: Add support to Murata SCL3300
    
    This driver adds support to Murata SCL3300 inclinometer.
    
    Signed-off-by: Alan C. Assis <[email protected]>
    Assisted-by: Claude Code
---
 drivers/sensors/CMakeLists.txt  |    4 +
 drivers/sensors/Kconfig         |   44 +
 drivers/sensors/Make.defs       |    4 +
 drivers/sensors/scl3300_uorb.c  | 1724 +++++++++++++++++++++++++++++++++++++++
 include/nuttx/sensors/ioctl.h   |   14 +
 include/nuttx/sensors/scl3300.h |  114 +++
 6 files changed, 1904 insertions(+)

diff --git a/drivers/sensors/CMakeLists.txt b/drivers/sensors/CMakeLists.txt
index 893ada9b9d3..87cdb25a10b 100644
--- a/drivers/sensors/CMakeLists.txt
+++ b/drivers/sensors/CMakeLists.txt
@@ -448,6 +448,10 @@ if(CONFIG_SENSORS)
       list(APPEND SRCS as5048a.c)
     endif()
 
+    if(CONFIG_SENSORS_SCL3300)
+      list(APPEND SRCS scl3300_uorb.c)
+    endif()
+
   endif() # CONFIG_SPI
 
   # These drivers depend on 1WIRE support
diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig
index 0cf4f6adba4..5fc9735b671 100644
--- a/drivers/sensors/Kconfig
+++ b/drivers/sensors/Kconfig
@@ -1996,6 +1996,50 @@ config SCD41_DEBUG
 
 endif # SENSORS_SCD41
 
+config SENSORS_SCL3300
+       bool "Murata SCL3300 3-axis inclinometer"
+       default n
+       select SPI
+       ---help---
+               Enable the uORB driver for the Murata SCL3300-D01 3-axis
+               inclinometer with SPI interface. Publishes sensor_inclinometer
+               and, optionally, sensor_accel topics. The operation mode and the
+               idle power policy are set by the board (struct scl3300_config_s)
+               and can be changed at run time with ioctls.
+
+if SENSORS_SCL3300
+
+config SENSORS_SCL3300_ACCEL
+       bool "Also publish the accelerometer topic"
+       default y
+
+config SENSORS_SCL3300_SPI_FREQUENCY
+       int "SPI frequency (Hz)"
+       default 2000000
+       range 100000 8000000
+
+config SENSORS_SCL3300_POLL
+       bool "Poll the sensor from a kernel thread"
+       default y
+       ---help---
+               The SCL3300 has no data-ready interrupt pin, so polling is the
+               normal mode. When disabled, data is only read on fetch().
+
+if SENSORS_SCL3300_POLL
+
+config SENSORS_SCL3300_POLL_INTERVAL
+       int "Default polling interval in microseconds"
+       default 10000
+       range 500 4294967295
+
+config SENSORS_SCL3300_THREAD_STACKSIZE
+       int "Worker thread stack size"
+       default 1024
+
+endif # SENSORS_SCL3300_POLL
+
+endif # SENSORS_SCL3300
+
 config SENSORS_SGP30
        bool "Sensirion SGP30 Gas Platform sensor"
        default n
diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs
index 306d57b9df2..1de83889e15 100644
--- a/drivers/sensors/Make.defs
+++ b/drivers/sensors/Make.defs
@@ -440,6 +440,10 @@ ifeq ($(CONFIG_SENSORS_AS5048A),y)
   CSRCS += as5048a.c
 endif
 
+ifeq ($(CONFIG_SENSORS_SCL3300),y)
+  CSRCS += scl3300_uorb.c
+endif
+
 endif # CONFIG_SPI
 
 # These drivers depend on 1WIRE support
diff --git a/drivers/sensors/scl3300_uorb.c b/drivers/sensors/scl3300_uorb.c
new file mode 100644
index 00000000000..da845bacb21
--- /dev/null
+++ b/drivers/sensors/scl3300_uorb.c
@@ -0,0 +1,1724 @@
+/****************************************************************************
+ * drivers/sensors/scl3300_uorb.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.
+ *
+ ****************************************************************************/
+
+/* Driver for the Murata SCL3300-D01 3-axis inclinometer.
+ *
+ * All section, table and figure numbers refer to the Murata SCL3300-D01
+ * Data Sheet, Doc.No. 4921, Rev. 4.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <nuttx/arch.h>
+#include <nuttx/clock.h>
+#include <nuttx/debug.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/kthread.h>
+#include <nuttx/mutex.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/signal.h>
+#include <nuttx/spi/spi.h>
+#include <nuttx/sensors/scl3300.h>
+#include <nuttx/sensors/sensor.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* SPI link (§5.1.1, §5.1.2, Table 8): mode 0, MSB first, 32-bit frames
+ * and at least 10 us with CS high between two frames (TLH).
+ */
+
+#define SCL3300_SPI_MODE          SPIDEV_MODE0
+#define SCL3300_SPI_NBITS         8
+#define SCL3300_FRAME_BYTES       4
+#define SCL3300_TLH_US            10
+
+/* SPI frame layout (Table 14): OP[31:26] = RW + ADDR[4:0], RS[25:24],
+ * DATA[23:8], CRC[7:0].
+ */
+
+#define SCL3300_FRAME_RW          (1u << 31)
+#define SCL3300_FRAME_ADDR_SHIFT  26
+#define SCL3300_FRAME_ADDR_MASK   0x1f
+#define SCL3300_FRAME_OP(f)       (((f) >> 26) & 0x3f)
+#define SCL3300_FRAME_RS(f)       (((f) >> 24) & 0x03)
+#define SCL3300_FRAME_DATA(f)     ((uint16_t)(((f) >> 8) & 0xffff))
+#define SCL3300_FRAME_CRC(f)      ((uint8_t)((f) & 0xff))
+
+/* Return status (§5.1.5, Table 15) */
+
+#define SCL3300_RS_STARTUP        0
+#define SCL3300_RS_NORMAL         1
+#define SCL3300_RS_ERROR          3
+
+/* CRC-8 (§5.2, Table 17, Fig. 15): polynomial x^8 + x^4 + x^3 + x^2 + 1,
+ * seed 0xff, result inverted, computed over frame bits 31..8.
+ */
+
+#define SCL3300_CRC_POLY          0x1d
+#define SCL3300_CRC_SEED          0xff
+
+/* Register addresses, bank 0 unless noted (Table 18, Table 19) */
+
+#define SCL3300_REG_ACC_X         0x01
+#define SCL3300_REG_ACC_Y         0x02
+#define SCL3300_REG_ACC_Z         0x03
+#define SCL3300_REG_STO           0x04
+#define SCL3300_REG_TEMP          0x05
+#define SCL3300_REG_STATUS        0x06
+#define SCL3300_REG_ERR_FLAG1     0x07
+#define SCL3300_REG_ERR_FLAG2     0x08
+#define SCL3300_REG_ANG_X         0x09
+#define SCL3300_REG_ANG_Y         0x0a
+#define SCL3300_REG_ANG_Z         0x0b
+#define SCL3300_REG_ANG_CTRL      0x0c
+#define SCL3300_REG_MODE          0x0d
+#define SCL3300_REG_WHOAMI        0x10
+#define SCL3300_REG_SERIAL1       0x19  /* Bank 1 */
+#define SCL3300_REG_SERIAL2       0x1a  /* Bank 1 */
+#define SCL3300_REG_SELBANK       0x1f
+
+/* Register values (Table 19, §6.5, §6.7, §6.9) */
+
+#define SCL3300_ANG_CTRL_ENABLE   0x001f
+#define SCL3300_MODE_CMD(m)       ((uint16_t)((m) - 1)) /* Mode 1..4 */
+#define SCL3300_MODE_POWERDOWN    0x0004
+#define SCL3300_MODE_SWRESET      0x0020
+#define SCL3300_WAKEUP_DATA       0x0000  /* Same frame as Mode 1 */
+#define SCL3300_WHOAMI_VALUE      0xc1
+
+/* STATUS register bits (Table 26, Table 27) */
+
+#define SCL3300_STATUS_PIN_CONT   (1 << 0)
+#define SCL3300_STATUS_MODE_CHG   (1 << 1)
+#define SCL3300_STATUS_PD         (1 << 2)
+#define SCL3300_STATUS_MEM        (1 << 3)
+#define SCL3300_STATUS_PWR        (1 << 4)
+#define SCL3300_STATUS_TEMP       (1 << 5)
+#define SCL3300_STATUS_SAT        (1 << 6)
+#define SCL3300_STATUS_CLK        (1 << 7)
+#define SCL3300_STATUS_DIGI2      (1 << 8)
+#define SCL3300_STATUS_DIGI1      (1 << 9)
+
+/* Flags that require a SW reset and a full start-up (§4.7 of the driver
+ * contract, derived from Table 27).
+ */
+
+#define SCL3300_STATUS_RESET      (SCL3300_STATUS_DIGI1 | \
+                                   SCL3300_STATUS_DIGI2 | \
+                                   SCL3300_STATUS_CLK | \
+                                   SCL3300_STATUS_MEM | \
+                                   SCL3300_STATUS_PWR | \
+                                   SCL3300_STATUS_PD | \
+                                   SCL3300_STATUS_MODE_CHG | \
+                                   SCL3300_STATUS_PIN_CONT)
+
+/* Flags expected right after a start-up (Table 27: PWR after power-up or
+ * reset, MODE_CHANGE after a mode change, PD after a wake-up).
+ */
+
+#define SCL3300_STATUS_STARTUP    (SCL3300_STATUS_PWR | \
+                                   SCL3300_STATUS_MODE_CHG | \
+                                   SCL3300_STATUS_PD)
+
+/* Timing (Table 11) */
+
+#define SCL3300_WAKEUP_US         3000
+#define SCL3300_RESET_US          3000
+
+/* Conversions.
+ *
+ * Angle (§2.5, §6.1.3): two's complement, deg = raw / 2^14 * 90.  Angles
+ * are reported signed: Table 9 and Table 18 show an unsigned 0..360 view,
+ * but §2.5 and §6.1.3 define the register as two's complement.
+ *
+ * Temperature (§2.4): degC = -273 + raw / 18.9.
+ *
+ * Acceleration (Table 2): m/s^2 = raw / sensitivity * g, with the
+ * datasheet definition of g ("Definition of gravitational acceleration:
+ * g = 9.819 m/s^2").
+ */
+
+#define SCL3300_G_MS2             9.819f
+#define SCL3300_G_UMS2            9819    /* g in mm/s^2 */
+#define SCL3300_ANGLE_FS_DEG      90
+#define SCL3300_ANGLE_FS_LSB      16384
+
+/* Self-test: number of consecutive STO samples checked (Table 23) */
+
+#define SCL3300_STO_SAMPLES       16
+
+/* Device info (Table 1, Table 2) */
+
+#define SCL3300_INFO_VERSION      1
+#define SCL3300_MIN_INTERVAL      500       /* us */
+#define SCL3300_MAX_INTERVAL      INT32_MAX /* us, no limit when polled */
+
+/* Consecutive failed bursts before a reset (driver policy) */
+
+#define SCL3300_MAX_BAD_BURSTS    3
+
+/* Minimum time between two saturation warnings */
+
+#define SCL3300_SAT_WARN_US       1000000
+
+#ifdef CONFIG_SENSORS_USE_B16
+#  define SCL3300_DATA_UNUSED     ((b16_t)b16MIN)
+#else
+#  define SCL3300_DATA_UNUSED     NAN
+#endif
+
+#ifndef CONFIG_SENSORS_SCL3300_POLL_INTERVAL
+#  define CONFIG_SENSORS_SCL3300_POLL_INTERVAL 10000
+#endif
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* Per-mode parameters (Table 2, Table 12, Table 23, §2.11.1) */
+
+struct scl3300_mode_s
+{
+  uint16_t sens;            /* Acceleration sensitivity, LSB/g */
+  uint16_t fs;              /* Acceleration full scale, LSB */
+  uint16_t settle_ms;       /* Settling time after mode change, ms */
+  uint16_t sto_thr;         /* Self-test output threshold, +/-LSB */
+};
+
+struct scl3300_dev_s;
+
+struct scl3300_sensor_s
+{
+  struct sensor_lowerhalf_s lower;       /* Must be first */
+  FAR struct scl3300_dev_s *dev;
+  uint32_t                  interval;    /* us */
+  uint64_t                  last;        /* Last publish timestamp */
+  bool                      enabled;
+};
+
+struct scl3300_dev_s
+{
+  struct scl3300_sensor_s   incl;
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+  struct scl3300_sensor_s   accel;
+#endif
+  FAR struct spi_dev_s     *spi;
+  int                       devno;
+  mutex_t                   lock;        /* Serializes SPI bursts and state */
+  uint8_t                   mode;        /* 1..4 */
+  uint8_t                   power;       /* enum scl3300_power_e */
+  bool                      powered;     /* Awake and started up */
+  bool                      failed;      /* Unrecoverable device fault */
+  uint8_t                   badbursts;   /* Consecutive failed bursts */
+  uint8_t                   recoveries;  /* Resets without a good sample */
+  uint16_t                  expected;    /* STATUS flags expected now */
+  uint32_t                  satdrops;    /* Samples dropped on saturation */
+  uint64_t                  satwarn;     /* Last saturation warning */
+#ifdef CONFIG_SENSORS_SCL3300_POLL
+  sem_t                     run;
+#endif
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* Sensor ops functions */
+
+static int scl3300_activate(FAR struct sensor_lowerhalf_s *lower,
+                            FAR struct file *filep, bool enable);
+static int scl3300_set_interval(FAR struct sensor_lowerhalf_s *lower,
+                                FAR struct file *filep,
+                                FAR uint32_t *period_us);
+#ifndef CONFIG_SENSORS_SCL3300_POLL
+static int scl3300_fetch(FAR struct sensor_lowerhalf_s *lower,
+                         FAR struct file *filep,
+                         FAR char *buffer, size_t buflen);
+#endif
+static int scl3300_selftest(FAR struct sensor_lowerhalf_s *lower,
+                            FAR struct file *filep, unsigned long arg);
+static int scl3300_get_info(FAR struct sensor_lowerhalf_s *lower,
+                            FAR struct file *filep,
+                            FAR struct sensor_device_info_s *info);
+static int scl3300_control(FAR struct sensor_lowerhalf_s *lower,
+                           FAR struct file *filep, int cmd,
+                           unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct sensor_ops_s g_scl3300_ops =
+{
+  NULL,                   /* open */
+  NULL,                   /* close */
+  scl3300_activate,       /* activate */
+  scl3300_set_interval,   /* set_interval */
+  NULL,                   /* batch */
+#ifdef CONFIG_SENSORS_SCL3300_POLL
+  NULL,                   /* fetch */
+#else
+  scl3300_fetch,          /* fetch */
+#endif
+  NULL,                   /* flush */
+  scl3300_selftest,       /* selftest */
+  NULL,                   /* set_calibvalue */
+  NULL,                   /* calibrate */
+  scl3300_get_info,       /* get_info */
+  NULL,                   /* set_nonwakeup */
+  scl3300_control,        /* control */
+};
+
+/* Indexed by mode - 1.  Sensitivities and settling times from Table 2 and
+ * Table 12, STO thresholds from Table 23.  Modes 3 and 4 are inclination
+ * modes valid for about +/-10 degrees of tilt only (§2.11.1); the full
+ * scale reported for them is 1 g, the largest acceleration seen in a
+ * static measurement.
+ */
+
+static const struct scl3300_mode_s g_scl3300_modes[] =
+{
+  {
+    6000, 7200, 25, 1800       /* Mode 1: +/-1.2 g, 40 Hz LPF */
+  },
+  {
+    3000, 7200, 15, 900        /* Mode 2: +/-2.4 g, 70 Hz LPF */
+  },
+  {
+    12000, 12000, 100, 3600    /* Mode 3: inclination, 10 Hz LPF */
+  },
+  {
+    12000, 12000, 100, 3600    /* Mode 4: inclination, low noise */
+  }
+};
+
+/* Current consumption in 0.1 mA per mode (Table 1) */
+
+static const uint8_t g_scl3300_power[] =
+{
+  12, 12, 12, 12
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: scl3300_crc8
+ *
+ * Description:
+ *   Compute the CRC-8 of an SPI frame over bits 31..8 (§5.2, Table 17).
+ *
+ ****************************************************************************/
+
+static uint8_t scl3300_crc8(uint32_t frame)
+{
+  uint8_t crc = SCL3300_CRC_SEED;
+  bool in;
+  bool msb;
+  int bit;
+
+  for (bit = 31; bit > 7; bit--)
+    {
+      in  = ((frame >> bit) & 1) != 0;
+      msb = (crc & 0x80) != 0;
+      crc = (uint8_t)(crc << 1);
+      if (in != msb)
+        {
+          crc ^= SCL3300_CRC_POLY;
+        }
+    }
+
+  return (uint8_t)~crc;
+}
+
+/****************************************************************************
+ * Name: scl3300_frame
+ *
+ * Description:
+ *   Build a MOSI frame (Table 14) including its CRC.
+ *
+ ****************************************************************************/
+
+static uint32_t scl3300_frame(bool rw, uint8_t addr, uint16_t data)
+{
+  uint32_t frame;
+
+  frame = ((uint32_t)(addr & SCL3300_FRAME_ADDR_MASK) <<
+           SCL3300_FRAME_ADDR_SHIFT) | ((uint32_t)data << 8);
+  if (rw)
+    {
+      frame |= SCL3300_FRAME_RW;
+    }
+
+  return frame | scl3300_crc8(frame);
+}
+
+/****************************************************************************
+ * Name: scl3300_response_ok
+ *
+ * Description:
+ *   Check that a MISO frame has a valid CRC and answers the request
+ *   'request', i.e. echoes its OP field (§6.1.1).
+ *
+ ****************************************************************************/
+
+static bool scl3300_response_ok(uint32_t response, uint32_t request)
+{
+  return scl3300_crc8(response) == SCL3300_FRAME_CRC(response) &&
+         SCL3300_FRAME_OP(response) == SCL3300_FRAME_OP(request);
+}
+
+#ifdef CONFIG_SENSORS_USE_B16
+/****************************************************************************
+ * Name: scl3300_divround
+ *
+ * Description:
+ *   Signed integer division rounded to nearest, den > 0.
+ *
+ ****************************************************************************/
+
+static b16_t scl3300_divround(int64_t num, int64_t den)
+{
+  if (num >= 0)
+    {
+      return (b16_t)((num + den / 2) / den);
+    }
+
+  return (b16_t)((num - den / 2) / den);
+}
+#endif
+
+/****************************************************************************
+ * Name: scl3300_angle
+ *
+ * Description:
+ *   Convert an ANG_X/Y/Z register value to degrees (§2.5, §6.1.3).
+ *
+ ****************************************************************************/
+
+static sensor_data_t scl3300_angle(int16_t raw)
+{
+#ifdef CONFIG_SENSORS_USE_B16
+  /* raw * 90 / 2^14 * 2^16 = raw * 360, exact */
+
+  return (b16_t)raw * (SCL3300_ANGLE_FS_DEG * 65536 / SCL3300_ANGLE_FS_LSB);
+#else
+  return (float)raw * ((float)SCL3300_ANGLE_FS_DEG /
+                       (float)SCL3300_ANGLE_FS_LSB);
+#endif
+}
+
+/****************************************************************************
+ * Name: scl3300_temp
+ *
+ * Description:
+ *   Convert a TEMPERATURE register value to degrees Celsius (§2.4).
+ *
+ ****************************************************************************/
+
+static sensor_data_t scl3300_temp(int16_t raw)
+{
+#ifdef CONFIG_SENSORS_USE_B16
+  /* raw / 18.9 * 2^16 = raw * 655360 / 189 */
+
+  return -itob16(273) + scl3300_divround((int64_t)raw * 655360, 189);
+#else
+  return -273.0f + (float)raw / 18.9f;
+#endif
+}
+
+/****************************************************************************
+ * Name: scl3300_accel
+ *
+ * Description:
+ *   Convert an ACC_X/Y/Z register value to m/s^2 (Table 2).
+ *
+ ****************************************************************************/
+
+static sensor_data_t scl3300_accel(int16_t raw, uint16_t sens)
+{
+#ifdef CONFIG_SENSORS_USE_B16
+  return scl3300_divround((int64_t)raw * SCL3300_G_UMS2 * 65536,
+                          (int64_t)sens * 1000);
+#else
+  return (float)raw * (SCL3300_G_MS2 / (float)sens);
+#endif
+}
+
+/****************************************************************************
+ * Name: scl3300_serial
+ *
+ * Description:
+ *   Compose the serial number from SERIAL1 and SERIAL2 (§6.8).
+ *
+ ****************************************************************************/
+
+static void scl3300_serial(uint16_t serial1, uint16_t serial2,
+                           FAR char *buf, size_t len)
+{
+  snprintf(buf, len, "%" PRIu32 "B33",
+           ((uint32_t)serial2 << 16) | serial1);
+}
+
+/****************************************************************************
+ * Name: scl3300_xfer
+ *
+ * Description:
+ *   Exchange 'n' frames in one SPI burst.  The protocol is off-frame
+ *   (§5.1.2, Fig. 13): rx[k] answers tx[k - 1] and rx[0] answers the last
+ *   frame of the previous burst.
+ *
+ ****************************************************************************/
+
+static void scl3300_xfer(FAR struct scl3300_dev_s *dev,
+                         FAR const uint32_t *tx, FAR uint32_t *rx, int n)
+{
+  uint8_t txbuf[SCL3300_FRAME_BYTES];
+  uint8_t rxbuf[SCL3300_FRAME_BYTES];
+  int i;
+
+  SPI_LOCK(dev->spi, true);
+  SPI_SETMODE(dev->spi, SCL3300_SPI_MODE);
+  SPI_SETBITS(dev->spi, SCL3300_SPI_NBITS);
+  SPI_SETFREQUENCY(dev->spi, CONFIG_SENSORS_SCL3300_SPI_FREQUENCY);
+
+  for (i = 0; i < n; i++)
+    {
+      txbuf[0] = (uint8_t)(tx[i] >> 24);
+      txbuf[1] = (uint8_t)(tx[i] >> 16);
+      txbuf[2] = (uint8_t)(tx[i] >> 8);
+      txbuf[3] = (uint8_t)tx[i];
+
+      SPI_SELECT(dev->spi, SPIDEV_ACCELEROMETER(dev->devno), true);
+      SPI_EXCHANGE(dev->spi, txbuf, rxbuf, SCL3300_FRAME_BYTES);
+      SPI_SELECT(dev->spi, SPIDEV_ACCELEROMETER(dev->devno), false);
+
+      /* Keep CS high for at least TLH before the next frame (Table 8) */
+
+      up_udelay(SCL3300_TLH_US);
+
+      if (rx != NULL)
+        {
+          rx[i] = ((uint32_t)rxbuf[0] << 24) | ((uint32_t)rxbuf[1] << 16) |
+                  ((uint32_t)rxbuf[2] << 8) | rxbuf[3];
+        }
+    }
+
+  SPI_LOCK(dev->spi, false);
+}
+
+/****************************************************************************
+ * Name: scl3300_write
+ *
+ * Description:
+ *   Send a single write command, discarding the response.
+ *
+ ****************************************************************************/
+
+static void scl3300_write(FAR struct scl3300_dev_s *dev, uint8_t addr,
+                          uint16_t data)
+{
+  uint32_t frame = scl3300_frame(true, addr, data);
+
+  scl3300_xfer(dev, &frame, NULL, 1);
+}
+
+/****************************************************************************
+ * Name: scl3300_startup
+ *
+ * Description:
+ *   Run the start-up sequence of Table 11 with the stored mode.  Also used
+ *   after every wake-up, reset and mode change, because power-down, SW
+ *   reset and power cycle all reset the written settings.
+ *
+ * Input Parameters:
+ *   dev  - Device state, locked by the caller.
+ *   wake - Send the wake-up command first.
+ *
+ * Returned Value:
+ *   OK, -EIO if the device does not report a successful start-up or
+ *   -ENODEV if WHOAMI does not match.
+ *
+ ****************************************************************************/
+
+static int scl3300_startup(FAR struct scl3300_dev_s *dev, bool wake)
+{
+  uint32_t tx[5];
+  uint32_t rx[5];
+  uint16_t whoami;
+
+  dev->powered = false;
+
+  /* Step 1: wake-up from power-down mode */
+
+  if (wake)
+    {
+      scl3300_write(dev, SCL3300_REG_MODE, SCL3300_WAKEUP_DATA);
+      nxsched_usleep(SCL3300_WAKEUP_US);
+    }
+
+  /* Step 2: SW reset.  The response of the next frame is undefined and is
+   * discarded below.
+   */
+
+  scl3300_write(dev, SCL3300_REG_MODE, SCL3300_MODE_SWRESET);
+  nxsched_usleep(SCL3300_RESET_US);
+
+  /* Steps 3 and 4: select the mode and enable the angle outputs */
+
+  tx[0] = scl3300_frame(true, SCL3300_REG_MODE,
+                        SCL3300_MODE_CMD(dev->mode));
+  tx[1] = scl3300_frame(true, SCL3300_REG_ANG_CTRL,
+                        SCL3300_ANG_CTRL_ENABLE);
+  scl3300_xfer(dev, tx, NULL, 2);
+
+  /* Step 5: wait for the output to settle */
+
+  nxsched_usleep(g_scl3300_modes[dev->mode - 1].settle_ms * 1000);
+
+  /* Steps 6 to 9: STATUS (clears the summary), STATUS, STATUS, WHOAMI and
+   * a trailing WHOAMI that clocks out the answer to the previous one.
+   */
+
+  tx[0] = scl3300_frame(false, SCL3300_REG_STATUS, 0);
+  tx[1] = tx[0];
+  tx[2] = tx[0];
+  tx[3] = scl3300_frame(false, SCL3300_REG_WHOAMI, 0);
+  tx[4] = tx[3];
+  scl3300_xfer(dev, tx, rx, 5);
+
+  /* Step 8: the response received during the third STATUS read must
+   * have RS = 01.
+   */
+
+  if (!scl3300_response_ok(rx[2], tx[1]) ||
+      SCL3300_FRAME_RS(rx[2]) != SCL3300_RS_NORMAL)
+    {
+      snerr("ERROR: start-up failed, response %08" PRIx32 "\n", rx[2]);
+      return -EIO;
+    }
+
+  /* Step 9: WHOAMI */
+
+  whoami = SCL3300_FRAME_DATA(rx[4]) & 0xff;
+  if (!scl3300_response_ok(rx[4], tx[3]) || whoami != SCL3300_WHOAMI_VALUE)
+    {
+      snerr("ERROR: wrong WHOAMI, response %08" PRIx32 "\n", rx[4]);
+      return -ENODEV;
+    }
+
+  dev->powered   = true;
+  dev->badbursts = 0;
+  dev->expected  = SCL3300_STATUS_STARTUP;
+  return OK;
+}
+
+/****************************************************************************
+ * Name: scl3300_powerdown
+ *
+ * Description:
+ *   Enter power-down mode (Table 19, §6.5).
+ *
+ ****************************************************************************/
+
+static void scl3300_powerdown(FAR struct scl3300_dev_s *dev)
+{
+  scl3300_write(dev, SCL3300_REG_MODE, SCL3300_MODE_POWERDOWN);
+  dev->powered = false;
+}
+
+/****************************************************************************
+ * Name: scl3300_any_enabled
+ ****************************************************************************/
+
+static bool scl3300_any_enabled(FAR struct scl3300_dev_s *dev)
+{
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+  return dev->incl.enabled || dev->accel.enabled;
+#else
+  return dev->incl.enabled;
+#endif
+}
+
+/****************************************************************************
+ * Name: scl3300_fail
+ *
+ * Description:
+ *   Mark the device as failed: stop publishing and log once.
+ *
+ ****************************************************************************/
+
+static void scl3300_fail(FAR struct scl3300_dev_s *dev)
+{
+  if (!dev->failed)
+    {
+      snerr("ERROR: SCL3300 %d failed, device disabled\n", dev->devno);
+      dev->failed = true;
+    }
+}
+
+/****************************************************************************
+ * Name: scl3300_recover
+ *
+ * Description:
+ *   SW reset and full start-up after a device fault.  If the fault came
+ *   back before a good sample was read since the last recovery, the reset
+ *   did not clear it and the device is marked failed.
+ *
+ ****************************************************************************/
+
+static void scl3300_recover(FAR struct scl3300_dev_s *dev)
+{
+  uint32_t tx[3];
+  uint32_t rx[3];
+
+  /* Read ERR_FLAG1 and ERR_FLAG2 for diagnostics only.  Note: the
+   * ERR_FLAG1 read frame is 1C0000E3 (Table 19).
+   */
+
+  tx[0] = scl3300_frame(false, SCL3300_REG_ERR_FLAG1, 0);
+  tx[1] = scl3300_frame(false, SCL3300_REG_ERR_FLAG2, 0);
+  tx[2] = scl3300_frame(false, SCL3300_REG_WHOAMI, 0);
+  scl3300_xfer(dev, tx, rx, 3);
+  snwarn("WARNING: resetting, ERR_FLAG1 %04x ERR_FLAG2 %04x\n",
+         SCL3300_FRAME_DATA(rx[1]), SCL3300_FRAME_DATA(rx[2]));
+
+  if (dev->recoveries++ > 0 || scl3300_startup(dev, false) < 0)
+    {
+      scl3300_fail(dev);
+    }
+}
+
+/****************************************************************************
+ * Name: scl3300_check_status
+ *
+ * Description:
+ *   Called after a burst returned RS = 11.  Reads STATUS and reacts as
+ *   required by Table 27.
+ *
+ * Returned Value:
+ *   OK if the sample may be published, -EAGAIN if it must be dropped.
+ *   'temp_ok' is cleared when the temperature is out of range.
+ *
+ ****************************************************************************/
+
+static int scl3300_check_status(FAR struct scl3300_dev_s *dev,
+                                FAR bool *temp_ok)
+{
+  uint32_t tx[3];
+  uint32_t rx[3];
+  uint16_t status;
+  uint16_t unexpected;
+  uint64_t now;
+
+  tx[0] = scl3300_frame(false, SCL3300_REG_STATUS, 0);
+  tx[1] = tx[0];
+  tx[2] = scl3300_frame(false, SCL3300_REG_WHOAMI, 0);
+  scl3300_xfer(dev, tx, rx, 3);
+
+  if (!scl3300_response_ok(rx[1], tx[0]))
+    {
+      return -EAGAIN;
+    }
+
+  status        = SCL3300_FRAME_DATA(rx[1]);
+  unexpected    = status & ~dev->expected;
+  dev->expected = 0;
+
+  /* RS = 11 with a clear STATUS means the error was caused by a CRC
+   * error in a MOSI frame, not by a sensor fault (§5.1.5).
+   */
+
+  if (status == 0)
+    {
+      return -EAGAIN;
+    }
+
+  if ((unexpected & SCL3300_STATUS_RESET) != 0)
+    {
+      snwarn("WARNING: STATUS %04x\n", status);
+      scl3300_recover(dev);
+      return -EAGAIN;
+    }
+
+  if ((status & SCL3300_STATUS_SAT) != 0)
+    {
+      /* Acceleration, angle and STO outputs are invalid (Table 27) */
+
+      dev->satdrops++;
+      now = sensor_get_timestamp();
+      if (now - dev->satwarn >= SCL3300_SAT_WARN_US)
+        {
+          snwarn("WARNING: saturated, %" PRIu32 " samples dropped\n",
+                 dev->satdrops);
+          dev->satwarn = now;
+        }
+
+      return -EAGAIN;
+    }
+
+  if ((status & SCL3300_STATUS_TEMP) != 0)
+    {
+      *temp_ok = false;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: scl3300_sample
+ *
+ * Description:
+ *   Read one sample in a single SPI burst.  Either output may be NULL.
+ *
+ * Returned Value:
+ *   OK if the sample is valid, a negated errno value if it was dropped.
+ *
+ ****************************************************************************/
+
+static int scl3300_sample(FAR struct scl3300_dev_s *dev,
+                          FAR struct sensor_inclinometer *incl,
+                          FAR struct sensor_accel *accel)
+{
+  uint32_t tx[8];
+  uint32_t rx[8];
+  int16_t raw[7];
+  uint64_t timestamp;
+  sensor_data_t temp;
+  uint16_t sens;
+  bool rserror = false;
+  bool temp_ok = true;
+  bool bad = false;
+  int itemp;
+  int iacc;
+  int iang;
+  int n = 0;
+  int ret;
+  int k;
+
+  if (dev->failed)
+    {
+      return -EIO;
+    }
+
+  if (!dev->powered)
+    {
+      return -EAGAIN;
+    }
+
+  iacc = n;
+  if (accel != NULL)
+    {
+      tx[n++] = scl3300_frame(false, SCL3300_REG_ACC_X, 0);
+      tx[n++] = scl3300_frame(false, SCL3300_REG_ACC_Y, 0);
+      tx[n++] = scl3300_frame(false, SCL3300_REG_ACC_Z, 0);
+    }
+
+  itemp = n;
+  tx[n++] = scl3300_frame(false, SCL3300_REG_TEMP, 0);
+
+  iang = n;
+  if (incl != NULL)
+    {
+      tx[n++] = scl3300_frame(false, SCL3300_REG_ANG_X, 0);
+      tx[n++] = scl3300_frame(false, SCL3300_REG_ANG_Y, 0);
+      tx[n++] = scl3300_frame(false, SCL3300_REG_ANG_Z, 0);
+    }
+
+  /* Trailing frame that clocks out the answer to the last read */
+
+  tx[n] = scl3300_frame(false, SCL3300_REG_WHOAMI, 0);
+
+  scl3300_xfer(dev, tx, rx, n + 1);
+  timestamp = sensor_get_timestamp();
+
+  for (k = 0; k < n; k++)
+    {
+      if (!scl3300_response_ok(rx[k + 1], tx[k]))
+        {
+          bad = true;
+          break;
+        }
+
+      switch (SCL3300_FRAME_RS(rx[k + 1]))
+        {
+          case SCL3300_RS_NORMAL:
+            break;
+
+          case SCL3300_RS_ERROR:
+            rserror = true;
+            break;
+
+          default:
+
+            /* Start-up in progress: drop the sample silently */
+
+            return -EAGAIN;
+        }
+
+      raw[k] = (int16_t)SCL3300_FRAME_DATA(rx[k + 1]);
+    }
+
+  if (bad)
+    {
+      if (++dev->badbursts >= SCL3300_MAX_BAD_BURSTS)
+        {
+          snwarn("WARNING: %d consecutive bad bursts\n", dev->badbursts);
+          scl3300_recover(dev);
+        }
+
+      return -EIO;
+    }
+
+  dev->badbursts = 0;
+
+  if (rserror)
+    {
+      ret = scl3300_check_status(dev, &temp_ok);
+      if (ret < 0)
+        {
+          return ret;
+        }
+    }
+  else
+    {
+      dev->expected = 0;
+    }
+
+  dev->recoveries = 0;
+
+  temp = temp_ok ? scl3300_temp(raw[itemp]) : SCL3300_DATA_UNUSED;
+  sens = g_scl3300_modes[dev->mode - 1].sens;
+
+  if (accel != NULL)
+    {
+      accel->timestamp   = timestamp;
+      accel->x           = scl3300_accel(raw[iacc], sens);
+      accel->y           = scl3300_accel(raw[iacc + 1], sens);
+      accel->z           = scl3300_accel(raw[iacc + 2], sens);
+      accel->temperature = temp;
+    }
+
+  if (incl != NULL)
+    {
+      incl->timestamp   = timestamp;
+      incl->x           = scl3300_angle(raw[iang]);
+      incl->y           = scl3300_angle(raw[iang + 1]);
+      incl->z           = scl3300_angle(raw[iang + 2]);
+      incl->temperature = temp;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: scl3300_wake
+ *
+ * Description:
+ *   Make sure the device is awake.  'wasdown' tells whether it was
+ *   powered down before, so that scl3300_restore() can put it back.
+ *
+ ****************************************************************************/
+
+static int scl3300_wake(FAR struct scl3300_dev_s *dev, FAR bool *wasdown)
+{
+  *wasdown = !dev->powered;
+  if (dev->powered)
+    {
+      return OK;
+    }
+
+  return scl3300_startup(dev, true);
+}
+
+/****************************************************************************
+ * Name: scl3300_idle
+ *
+ * Description:
+ *   Apply the idle power policy when no topic is active.
+ *
+ ****************************************************************************/
+
+static void scl3300_idle(FAR struct scl3300_dev_s *dev)
+{
+  if (dev->power == SCL3300_POWER_DOWN_IDLE && !scl3300_any_enabled(dev) &&
+      dev->powered)
+    {
+      scl3300_powerdown(dev);
+    }
+}
+
+/****************************************************************************
+ * Name: scl3300_restore
+ *
+ * Description:
+ *   Undo scl3300_wake().
+ *
+ ****************************************************************************/
+
+static void scl3300_restore(FAR struct scl3300_dev_s *dev, bool wasdown)
+{
+  if (wasdown && !scl3300_any_enabled(dev) && dev->powered)
+    {
+      scl3300_powerdown(dev);
+    }
+}
+
+/****************************************************************************
+ * Name: scl3300_activate
+ ****************************************************************************/
+
+static int scl3300_activate(FAR struct sensor_lowerhalf_s *lower,
+                            FAR struct file *filep, bool enable)
+{
+  FAR struct scl3300_sensor_s *sensor = (FAR struct scl3300_sensor_s *)lower;
+  FAR struct scl3300_dev_s *dev = sensor->dev;
+  bool wasidle;
+  int ret = OK;
+
+  nxmutex_lock(&dev->lock);
+
+  wasidle = !scl3300_any_enabled(dev);
+
+  if (enable)
+    {
+      if (dev->failed)
+        {
+          ret = -EIO;
+          goto out;
+        }
+
+      if (!dev->powered)
+        {
+          ret = scl3300_startup(dev, true);
+          if (ret < 0)
+            {
+              goto out;
+            }
+
+          dev->recoveries = 0;
+        }
+
+      sensor->last    = 0;
+      sensor->enabled = true;
+
+#ifdef CONFIG_SENSORS_SCL3300_POLL
+      if (wasidle)
+        {
+          nxsem_post(&dev->run);
+        }
+#endif
+    }
+  else
+    {
+      sensor->enabled = false;
+      scl3300_idle(dev);
+    }
+
+out:
+  nxmutex_unlock(&dev->lock);
+  UNUSED(wasidle);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: scl3300_set_interval
+ ****************************************************************************/
+
+static int scl3300_set_interval(FAR struct sensor_lowerhalf_s *lower,
+                                FAR struct file *filep,
+                                FAR uint32_t *period_us)
+{
+  FAR struct scl3300_sensor_s *sensor = (FAR struct scl3300_sensor_s *)lower;
+  FAR struct scl3300_dev_s *dev = sensor->dev;
+
+  if (*period_us < SCL3300_MIN_INTERVAL)
+    {
+      *period_us = SCL3300_MIN_INTERVAL;
+    }
+
+  nxmutex_lock(&dev->lock);
+  sensor->interval = *period_us;
+  nxmutex_unlock(&dev->lock);
+
+  return OK;
+}
+
+#ifndef CONFIG_SENSORS_SCL3300_POLL
+/****************************************************************************
+ * Name: scl3300_fetch
+ ****************************************************************************/
+
+static int scl3300_fetch(FAR struct sensor_lowerhalf_s *lower,
+                         FAR struct file *filep,
+                         FAR char *buffer, size_t buflen)
+{
+  FAR struct scl3300_sensor_s *sensor = (FAR struct scl3300_sensor_s *)lower;
+  FAR struct scl3300_dev_s *dev = sensor->dev;
+  bool wasdown;
+  int ret;
+
+  if (buflen < (lower->type == SENSOR_TYPE_INCLINOMETER ?
+                 sizeof(struct sensor_inclinometer) :
+                 sizeof(struct sensor_accel)))
+    {
+      return -EINVAL;
+    }
+
+  nxmutex_lock(&dev->lock);
+
+  if (dev->failed)
+    {
+      ret = -EIO;
+      goto out;
+    }
+
+  ret = scl3300_wake(dev, &wasdown);
+  if (ret < 0)
+    {
+      goto out;
+    }
+
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+  if (lower->type == SENSOR_TYPE_ACCELEROMETER)
+    {
+      ret = scl3300_sample(dev, NULL, (FAR struct sensor_accel *)buffer);
+      if (ret >= 0)
+        {
+          ret = sizeof(struct sensor_accel);
+        }
+    }
+  else
+#endif
+    {
+      ret = scl3300_sample(dev, (FAR struct sensor_inclinometer *)buffer,
+                           NULL);
+      if (ret >= 0)
+        {
+          ret = sizeof(struct sensor_inclinometer);
+        }
+    }
+
+  scl3300_restore(dev, wasdown);
+
+out:
+  nxmutex_unlock(&dev->lock);
+  return ret;
+}
+#endif
+
+/****************************************************************************
+ * Name: scl3300_selftest
+ *
+ * Description:
+ *   Check WHOAMI, check that STATUS reports no fault and that
+ *   SCL3300_STO_SAMPLES consecutive self-test outputs are within the
+ *   threshold of the current mode (Table 23).
+ *
+ ****************************************************************************/
+
+static int scl3300_selftest(FAR struct sensor_lowerhalf_s *lower,
+                            FAR struct file *filep, unsigned long arg)
+{
+  FAR struct scl3300_sensor_s *sensor = (FAR struct scl3300_sensor_s *)lower;
+  FAR struct scl3300_dev_s *dev = sensor->dev;
+  uint32_t tx[SCL3300_STO_SAMPLES + 4];
+  uint32_t rx[SCL3300_STO_SAMPLES + 4];
+  int16_t thr;
+  int16_t sto;
+  bool wasdown;
+  int ret;
+  int k;
+
+  nxmutex_lock(&dev->lock);
+
+  if (dev->failed)
+    {
+      ret = -EIO;
+      goto out;
+    }
+
+  ret = scl3300_wake(dev, &wasdown);
+  if (ret < 0)
+    {
+      ret = -EIO;
+      goto out;
+    }
+
+  /* WHOAMI, STATUS (clears the summary), STATUS, STO x N, trailer */
+
+  tx[0] = scl3300_frame(false, SCL3300_REG_WHOAMI, 0);
+  tx[1] = scl3300_frame(false, SCL3300_REG_STATUS, 0);
+  tx[2] = tx[1];
+  for (k = 0; k < SCL3300_STO_SAMPLES; k++)
+    {
+      tx[3 + k] = scl3300_frame(false, SCL3300_REG_STO, 0);
+    }
+
+  tx[3 + k] = tx[0];
+  scl3300_xfer(dev, tx, rx, SCL3300_STO_SAMPLES + 4);
+
+  ret = OK;
+
+  if (!scl3300_response_ok(rx[1], tx[0]) ||
+      (SCL3300_FRAME_DATA(rx[1]) & 0xff) != SCL3300_WHOAMI_VALUE)
+    {
+      snerr("ERROR: self-test WHOAMI %08" PRIx32 "\n", rx[1]);
+      ret = -EIO;
+    }
+
+  if (!scl3300_response_ok(rx[3], tx[2]) ||
+      (SCL3300_FRAME_DATA(rx[3]) &
+       (SCL3300_STATUS_RESET | SCL3300_STATUS_SAT)) != 0)
+    {
+      snerr("ERROR: self-test STATUS %08" PRIx32 "\n", rx[3]);
+      ret = -EIO;
+    }
+
+  thr = (int16_t)g_scl3300_modes[dev->mode - 1].sto_thr;
+  for (k = 0; k < SCL3300_STO_SAMPLES; k++)
+    {
+      sto = (int16_t)SCL3300_FRAME_DATA(rx[4 + k]);
+      if (!scl3300_response_ok(rx[4 + k], tx[3 + k]) ||
+          SCL3300_FRAME_RS(rx[4 + k]) != SCL3300_RS_NORMAL ||
+          sto > thr || sto < -thr)
+        {
+          snerr("ERROR: self-test STO %08" PRIx32 "\n", rx[4 + k]);
+          ret = -EIO;
+          break;
+        }
+    }
+
+  scl3300_restore(dev, wasdown);
+
+out:
+  nxmutex_unlock(&dev->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: scl3300_get_info
+ ****************************************************************************/
+
+static int scl3300_get_info(FAR struct sensor_lowerhalf_s *lower,
+                            FAR struct file *filep,
+                            FAR struct sensor_device_info_s *info)
+{
+  FAR struct scl3300_sensor_s *sensor = (FAR struct scl3300_sensor_s *)lower;
+  FAR struct scl3300_dev_s *dev = sensor->dev;
+  FAR const struct scl3300_mode_s *mode;
+
+  nxmutex_lock(&dev->lock);
+  mode = &g_scl3300_modes[dev->mode - 1];
+
+  memset(info, 0, sizeof(*info));
+  info->version   = SCL3300_INFO_VERSION;
+  info->power     = sensor_data_divi(
+                      sensor_data_itof(g_scl3300_power[dev->mode - 1]), 10);
+  info->min_delay = SCL3300_MIN_INTERVAL;
+  info->max_delay = SCL3300_MAX_INTERVAL;
+  strlcpy(info->name, "SCL3300", sizeof(info->name));
+  strlcpy(info->vendor, "Murata", sizeof(info->vendor));
+
+  if (lower->type == SENSOR_TYPE_INCLINOMETER)
+    {
+      info->max_range  = sensor_data_itof(SCL3300_ANGLE_FS_DEG);
+      info->resolution = scl3300_angle(1);
+    }
+  else
+    {
+      info->max_range  = scl3300_accel((int16_t)mode->fs, mode->sens);
+      info->resolution = scl3300_accel(1, mode->sens);
+    }
+
+  nxmutex_unlock(&dev->lock);
+  return OK;
+}
+
+/****************************************************************************
+ * Name: scl3300_control
+ ****************************************************************************/
+
+static int scl3300_control(FAR struct sensor_lowerhalf_s *lower,
+                           FAR struct file *filep, int cmd,
+                           unsigned long arg)
+{
+  FAR struct scl3300_sensor_s *sensor = (FAR struct scl3300_sensor_s *)lower;
+  FAR struct scl3300_dev_s *dev = sensor->dev;
+  FAR uint8_t *whoami;
+  uint32_t tx[2];
+  uint32_t rx[2];
+  bool wasdown;
+  int ret = OK;
+
+  nxmutex_lock(&dev->lock);
+
+  switch (cmd)
+    {
+      /* Arg: enum scl3300_mode_e by value */
+
+      case SNIOC_SET_OPERATIONAL_MODE:
+        if (arg < SCL3300_MODE_1 || arg > SCL3300_MODE_4)
+          {
+            ret = -EINVAL;
+            break;
+          }
+
+        dev->mode = (uint8_t)arg;
+        if (dev->powered)
+          {
+            ret = scl3300_startup(dev, false);
+            if (ret < 0)
+              {
+                scl3300_fail(dev);
+              }
+          }
+        break;
+
+      /* Arg: enum scl3300_power_e by value */
+
+      case SNIOC_SET_POWER_MODE:
+        if (arg != SCL3300_POWER_ALWAYS_ON && arg != SCL3300_POWER_DOWN_IDLE)
+          {
+            ret = -EINVAL;
+            break;
+          }
+
+        dev->power = (uint8_t)arg;
+        if (dev->power == SCL3300_POWER_ALWAYS_ON)
+          {
+            if (!dev->powered && !dev->failed)
+              {
+                ret = scl3300_startup(dev, true);
+                if (ret < 0)
+                  {
+                    scl3300_fail(dev);
+                  }
+              }
+          }
+        else
+          {
+            scl3300_idle(dev);
+          }
+        break;
+
+      /* Arg: none.  Also clears a failed state if the start-up succeeds. */
+
+      case SNIOC_RESET:
+        ret = scl3300_startup(dev, true);
+        if (ret >= 0)
+          {
+            dev->failed     = false;
+            dev->recoveries = 0;
+            scl3300_idle(dev);
+          }
+        else
+          {
+            scl3300_fail(dev);
+          }
+        break;
+
+      /* Arg: FAR uint8_t * */
+
+      case SNIOC_WHO_AM_I:
+        whoami = (FAR uint8_t *)(uintptr_t)arg;
+        if (whoami == NULL)
+          {
+            ret = -EINVAL;
+            break;
+          }
+
+        if (dev->failed)
+          {
+            ret = -EIO;
+            break;
+          }
+
+        ret = scl3300_wake(dev, &wasdown);
+        if (ret < 0)
+          {
+            break;
+          }
+
+        tx[0] = scl3300_frame(false, SCL3300_REG_WHOAMI, 0);
+        tx[1] = tx[0];
+        scl3300_xfer(dev, tx, rx, 2);
+        if (scl3300_response_ok(rx[1], tx[0]))
+          {
+            *whoami = SCL3300_FRAME_DATA(rx[1]) & 0xff;
+          }
+        else
+          {
+            ret = -EIO;
+          }
+
+        scl3300_restore(dev, wasdown);
+        break;
+
+      default:
+        ret = -ENOTTY;
+        break;
+    }
+
+  nxmutex_unlock(&dev->lock);
+  return ret;
+}
+
+/****************************************************************************
+ * Name: scl3300_read_serial
+ *
+ * Description:
+ *   Read and log the serial number (§6.8).  Note: §6.8 refers to "6.5 CMD"
+ *   for the bank switch, but the bank is selected with SELBANK (§6.9).
+ *   The switch back to bank 0 is part of the same burst, so bank 1 is
+ *   never left selected.
+ *
+ ****************************************************************************/
+
+static void scl3300_read_serial(FAR struct scl3300_dev_s *dev)
+{
+  uint32_t tx[5];
+  uint32_t rx[5];
+  char serial[24];
+
+  tx[0] = scl3300_frame(true, SCL3300_REG_SELBANK, 1);
+  tx[1] = scl3300_frame(false, SCL3300_REG_SERIAL1, 0);
+  tx[2] = scl3300_frame(false, SCL3300_REG_SERIAL2, 0);
+  tx[3] = scl3300_frame(true, SCL3300_REG_SELBANK, 0);
+  tx[4] = scl3300_frame(false, SCL3300_REG_WHOAMI, 0);
+  scl3300_xfer(dev, tx, rx, 5);
+
+  if (scl3300_response_ok(rx[2], tx[1]) &&
+      scl3300_response_ok(rx[3], tx[2]))
+    {
+      scl3300_serial(SCL3300_FRAME_DATA(rx[2]), SCL3300_FRAME_DATA(rx[3]),
+                     serial, sizeof(serial));
+      sninfo("SCL3300 %d: WHOAMI ok, serial number %s\n", dev->devno,
+             serial);
+    }
+
+  UNUSED(serial);
+}
+
+#ifdef CONFIG_SENSORS_SCL3300_POLL
+/****************************************************************************
+ * Name: scl3300_due
+ *
+ * Description:
+ *   Tell whether a topic must be published now.  'slack' absorbs the
+ *   wake-up jitter of the polling thread.
+ *
+ ****************************************************************************/
+
+static bool scl3300_due(FAR struct scl3300_sensor_s *sensor, uint64_t now,
+                        uint32_t slack)
+{
+  return sensor->enabled &&
+         (sensor->last == 0 || now + slack >= sensor->last +
+                                              sensor->interval);
+}
+
+/****************************************************************************
+ * Name: scl3300_thread
+ *
+ * Description:
+ *   Polling thread.  Sleeps for the shortest interval of the active topics
+ *   and publishes each topic when its own interval has elapsed.
+ *
+ ****************************************************************************/
+
+static int scl3300_thread(int argc, FAR char **argv)
+{
+  FAR struct scl3300_dev_s *dev =
+    (FAR struct scl3300_dev_s *)((uintptr_t)strtoul(argv[1], NULL, 16));
+  struct sensor_inclinometer incl;
+  FAR struct sensor_inclinometer *pincl;
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+  struct sensor_accel accel;
+#endif
+  FAR struct sensor_accel *paccel;
+  struct timespec ts;
+  uint32_t interval;
+  uint64_t next = 0;
+  uint64_t now;
+  int ret;
+
+  while (true)
+    {
+      nxmutex_lock(&dev->lock);
+
+      if (!scl3300_any_enabled(dev))
+        {
+          nxmutex_unlock(&dev->lock);
+          nxsem_wait_uninterruptible(&dev->run);
+          next = 0;
+          continue;
+        }
+
+      interval = UINT32_MAX;
+      if (dev->incl.enabled)
+        {
+          interval = dev->incl.interval;
+        }
+
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+      if (dev->accel.enabled && dev->accel.interval < interval)
+        {
+          interval = dev->accel.interval;
+        }
+#endif
+
+      now    = sensor_get_timestamp();
+      pincl  = scl3300_due(&dev->incl, now, interval / 2) ? &incl : NULL;
+      paccel = NULL;
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+      if (scl3300_due(&dev->accel, now, interval / 2))
+        {
+          paccel = &accel;
+        }
+#endif
+
+      ret = -EAGAIN;
+      if (pincl != NULL || paccel != NULL)
+        {
+          ret = scl3300_sample(dev, pincl, paccel);
+          if (ret >= 0 && pincl != NULL)
+            {
+              dev->incl.last = incl.timestamp;
+            }
+
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+          if (ret >= 0 && paccel != NULL)
+            {
+              dev->accel.last = accel.timestamp;
+            }
+#endif
+        }
+
+      nxmutex_unlock(&dev->lock);
+
+      /* Publish without holding dev->lock: the upper half calls
+       * activate() with its own lock held, and push_event() takes it.
+       */
+
+      if (ret >= 0 && pincl != NULL)
+        {
+          dev->incl.lower.push_event(dev->incl.lower.priv, &incl,
+                                     sizeof(incl));
+        }
+
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+      if (ret >= 0 && paccel != NULL)
+        {
+          dev->accel.lower.push_event(dev->accel.lower.priv, &accel,
+                                      sizeof(accel));
+        }
+#endif
+
+      /* Wait for an absolute deadline, so that neither the time spent
+       * above nor the tick rounding of a relative sleep accumulates.  If
+       * a whole period was missed, restart from now instead of catching
+       * up.  An activation posts 'run' and ends the wait early.
+       */
+
+      next += interval;
+      if (next <= now)
+        {
+          next = now + interval;
+        }
+
+      ts.tv_sec  = next / USEC_PER_SEC;
+      ts.tv_nsec = (next % USEC_PER_SEC) * NSEC_PER_USEC;
+      nxsem_clockwait_uninterruptible(&dev->run, CLOCK_MONOTONIC, &ts);
+    }
+
+  return OK;
+}
+#endif
+
+/****************************************************************************
+ * Name: scl3300_init_sensor
+ ****************************************************************************/
+
+static void scl3300_init_sensor(FAR struct scl3300_dev_s *dev,
+                                FAR struct scl3300_sensor_s *sensor,
+                                int type)
+{
+  sensor->lower.ops     = &g_scl3300_ops;
+  sensor->lower.type    = type;
+  sensor->lower.nbuffer = 1;
+  sensor->dev           = dev;
+  sensor->interval      = CONFIG_SENSORS_SCL3300_POLL_INTERVAL;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: scl3300_register
+ *
+ * Description:
+ *   Register the Murata SCL3300 inclinometer as the uORB topics
+ *   sensor_inclinometer<devno> and, if CONFIG_SENSORS_SCL3300_ACCEL is
+ *   enabled, sensor_accel<devno>.  The chip select used is
+ *   SPIDEV_ACCELEROMETER(devno).
+ *
+ * Input Parameters:
+ *   devno  - Instance number of the uORB topics and of the chip select.
+ *   spi    - An instance of the SPI interface to use to communicate with
+ *            the SCL3300.
+ *   config - Platform data, copied by the driver.  NULL selects Mode 1
+ *            and SCL3300_POWER_ALWAYS_ON.
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int scl3300_register(int devno, FAR struct spi_dev_s *spi,
+                     FAR const struct scl3300_config_s *config)
+{
+  FAR struct scl3300_dev_s *dev;
+  uint8_t mode = SCL3300_MODE_1;
+  uint8_t power = SCL3300_POWER_ALWAYS_ON;
+#ifdef CONFIG_SENSORS_SCL3300_POLL
+  FAR char *argv[2];
+  char arg1[32];
+#endif
+  int ret;
+
+  DEBUGASSERT(spi != NULL);
+
+  if (config != NULL)
+    {
+      if (config->mode > SCL3300_MODE_4 ||
+          (config->power != SCL3300_POWER_ALWAYS_ON &&
+           config->power != SCL3300_POWER_DOWN_IDLE))
+        {
+          return -EINVAL;
+        }
+
+      if (config->mode != 0)
+        {
+          mode = config->mode;
+        }
+
+      power = config->power;
+    }
+
+  dev = kmm_zalloc(sizeof(struct scl3300_dev_s));
+  if (dev == NULL)
+    {
+      return -ENOMEM;
+    }
+
+  dev->spi   = spi;
+  dev->devno = devno;
+  dev->mode  = mode;
+  dev->power = power;
+  nxmutex_init(&dev->lock);
+
+  scl3300_init_sensor(dev, &dev->incl, SENSOR_TYPE_INCLINOMETER);
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+  scl3300_init_sensor(dev, &dev->accel, SENSOR_TYPE_ACCELEROMETER);
+#endif
+
+  /* The device may have been left in power-down by a previous run, so
+   * always start with a wake-up.
+   */
+
+  ret = scl3300_startup(dev, true);
+  if (ret < 0)
+    {
+      goto err_dev;
+    }
+
+  scl3300_read_serial(dev);
+  scl3300_idle(dev);
+
+  ret = sensor_register(&dev->incl.lower, devno);
+  if (ret < 0)
+    {
+      snerr("ERROR: failed to register inclinometer: %d\n", ret);
+      goto err_dev;
+    }
+
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+  ret = sensor_register(&dev->accel.lower, devno);
+  if (ret < 0)
+    {
+      snerr("ERROR: failed to register accelerometer: %d\n", ret);
+      goto err_incl;
+    }
+#endif
+
+#ifdef CONFIG_SENSORS_SCL3300_POLL
+  nxsem_init(&dev->run, 0, 0);
+
+  snprintf(arg1, sizeof(arg1), "%p", dev);
+  argv[0] = arg1;
+  argv[1] = NULL;
+  ret = kthread_create("scl3300_thread", SCHED_PRIORITY_DEFAULT,
+                       CONFIG_SENSORS_SCL3300_THREAD_STACKSIZE,
+                       scl3300_thread, argv);
+  if (ret < 0)
+    {
+      snerr("ERROR: failed to create the poll thread: %d\n", ret);
+      goto err_sem;
+    }
+#endif
+
+  return OK;
+
+#ifdef CONFIG_SENSORS_SCL3300_POLL
+err_sem:
+  nxsem_destroy(&dev->run);
+#  ifdef CONFIG_SENSORS_SCL3300_ACCEL
+  sensor_unregister(&dev->accel.lower, devno);
+#  endif
+#endif
+#ifdef CONFIG_SENSORS_SCL3300_ACCEL
+err_incl:
+#endif
+  sensor_unregister(&dev->incl.lower, devno);
+err_dev:
+  nxmutex_destroy(&dev->lock);
+  kmm_free(dev);
+  return ret;
+}
diff --git a/include/nuttx/sensors/ioctl.h b/include/nuttx/sensors/ioctl.h
index 42099a63fd6..a1fa3771627 100644
--- a/include/nuttx/sensors/ioctl.h
+++ b/include/nuttx/sensors/ioctl.h
@@ -264,6 +264,20 @@
 
 #define SNIOC_SET_MOVING_AVG       _SNIOC(0x006e)
 
+/* IOCTL commands unique to SCL3300 */
+
+/* Command:      SNIOC_SET_OPERATIONAL_MODE
+ * Description:  Select the operation mode
+ *               Reuse from ISL29023
+ * Arg:          enum scl3300_mode_e by value
+ */
+
+/* Command:      SNIOC_SET_POWER_MODE
+ * Description:  Select the idle power policy
+ *               Reuse from LIS3DH
+ * Arg:          enum scl3300_power_e by value
+ */
+
 /* Command:      SNIOC_GET_STATE
  * Description:  Get state for all subscribers, include min_interval,
  *               min_latency and the number of subscribers.
diff --git a/include/nuttx/sensors/scl3300.h b/include/nuttx/sensors/scl3300.h
new file mode 100644
index 00000000000..de8ca1466b4
--- /dev/null
+++ b/include/nuttx/sensors/scl3300.h
@@ -0,0 +1,114 @@
+/****************************************************************************
+ * include/nuttx/sensors/scl3300.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_SCL3300_H
+#define __INCLUDE_NUTTX_SENSORS_SCL3300_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+
+#include <nuttx/spi/spi.h>
+
+/****************************************************************************
+ * Public Types
+ ****************************************************************************/
+
+/* Operation modes (datasheet Table 2 and Table 12).  Used in
+ * struct scl3300_config_s and as the argument of
+ * SNIOC_SET_OPERATIONAL_MODE.
+ */
+
+enum scl3300_mode_e
+{
+  SCL3300_MODE_1 = 1,             /* +/-1.2 g, 40 Hz LPF (default) */
+  SCL3300_MODE_2 = 2,             /* +/-2.4 g, 70 Hz LPF */
+  SCL3300_MODE_3 = 3,             /* Inclination mode, 10 Hz LPF */
+  SCL3300_MODE_4 = 4              /* Inclination mode, 10 Hz LPF, low noise */
+};
+
+/* Idle power policy.  Used in struct scl3300_config_s and as the argument
+ * of SNIOC_SET_POWER_MODE.
+ */
+
+enum scl3300_power_e
+{
+  SCL3300_POWER_ALWAYS_ON = 0,    /* Stay powered when idle (default) */
+  SCL3300_POWER_DOWN_IDLE = 1     /* Power down when no topic is active */
+};
+
+/* Platform data. Pass NULL to scl3300_register() for the defaults:
+ * Mode 1 and SCL3300_POWER_ALWAYS_ON.
+ */
+
+struct scl3300_config_s
+{
+  uint8_t mode;                   /* enum scl3300_mode_e, 0 = default */
+  uint8_t power;                  /* enum scl3300_power_e */
+};
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: scl3300_register
+ *
+ * Description:
+ *   Register the Murata SCL3300 inclinometer as the uORB topics
+ *   sensor_inclinometer<devno> and, if CONFIG_SENSORS_SCL3300_ACCEL is
+ *   enabled, sensor_accel<devno>.  The chip select used is
+ *   SPIDEV_ACCELEROMETER(devno).
+ *
+ * Input Parameters:
+ *   devno  - Instance number of the uORB topics and of the chip select.
+ *   spi    - An instance of the SPI interface to use to communicate with
+ *            the SCL3300.
+ *   config - Platform data, copied by the driver.  NULL selects Mode 1
+ *            and SCL3300_POWER_ALWAYS_ON.
+ *
+ * Returned Value:
+ *   Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int scl3300_register(int devno, FAR struct spi_dev_s *spi,
+                     FAR const struct scl3300_config_s *config);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __INCLUDE_NUTTX_SENSORS_SCL3300_H */

Reply via email to