From 044879e7a34bf973e784d4389b049d36ea5fd53d Mon Sep 17 00:00:00 2001 From: Matteo Golin Date: Sat, 25 Jan 2025 10:36:13 -0500 Subject: [PATCH] sensors/lis2mdl: Create a UORB driver for the LIS2MDL magnetometer. --- .../components/drivers/special/sensors.rst | 1 + .../drivers/special/sensors/lis2mdl.rst | 163 ++ .../special/sensors/sensors_legacy.rst | 1 - .../drivers/special/sensors/sensors_uorb.rst | 2 + drivers/sensors/CMakeLists.txt | 4 + drivers/sensors/Kconfig | 28 + drivers/sensors/Make.defs | 4 + drivers/sensors/lis2mdl_uorb.c | 1391 +++++++++++++++++ include/nuttx/sensors/ioctl.h | 7 + include/nuttx/sensors/lis2mdl.h | 64 + 10 files changed, 1664 insertions(+), 1 deletion(-) create mode 100644 Documentation/components/drivers/special/sensors/lis2mdl.rst create mode 100644 drivers/sensors/lis2mdl_uorb.c create mode 100644 include/nuttx/sensors/lis2mdl.h diff --git a/Documentation/components/drivers/special/sensors.rst b/Documentation/components/drivers/special/sensors.rst index 6891bebec21..b19e5160725 100644 --- a/Documentation/components/drivers/special/sensors.rst +++ b/Documentation/components/drivers/special/sensors.rst @@ -30,3 +30,4 @@ general interafce. sensors/mcp9600.rst sensors/mpl115a.rst sensors/sht4x.rst + sensors/lis2mdl.rst diff --git a/Documentation/components/drivers/special/sensors/lis2mdl.rst b/Documentation/components/drivers/special/sensors/lis2mdl.rst new file mode 100644 index 00000000000..48a85c85110 --- /dev/null +++ b/Documentation/components/drivers/special/sensors/lis2mdl.rst @@ -0,0 +1,163 @@ +======= +LIS2MDL +======= + +Contributed by Matteo Golin. + +The LIS2MDL is a low power, high-performance 3-axis magnetometer by ST +Microelectronics. It has I2C and SPI interfaces, although this driver currently +only supports I2C. + +The driver uses the :doc:`uorb +` interface. It supports the +self-test capability + +Application Programming Interface +================================= + +.. code-block:: c + + #include + +The LIS2MDL registration function allows the driver to be registered as a UORB +driver. Registering this driver will cause the ``/dev/uorb/sensor_mag`` topic +to appear, where ``n`` is the value of ``devno``. + +The driver can be registered either in polling mode or in interrupt-driven mode. +The polling mode will create a kernel thread to poll the sensor periodically +according to the set interval. Polling mode is registered with ``NULL`` instead +of a real function pointer to the ``attach`` parameter, like so: + +.. code-block:: c + + int err; + + /* Creates /dev/uorb/mag0 in polled mode */ + + err = lis2mdl_register(i2c_master, 0, 0x1e, NULL); + if (err < 0) + { + syslog(LOG_ERR, "Could not register LIS2MDL driver at 0x1E: %d\n", err); + } + +To register in interrupt-driven mode, a function must be provided by the +calling code to register the LIS2MDL interrupt handler properly. This function +should take the interrupt handler and ``arg`` reference as arguments, and return +0 on success or a negated error code on failure. This function must also enable +the interrupt after it is registered successfully. + +.. warning:: + To use interrupt-driven mode, ``CONFIG_SCHED_HPWORK`` must be enabled. + +The example below shows the example process for an RP2040 based board, but it +will be similar on other architectures: + +.. code-block:: c + + /* IRQ attach function example for RP2040 board */ + + static int board_lis2mdl_attach(xcpt_t handler, FAR void *arg) + { + int err; + err = rp2040_gpio_irq_attach(GPIO_MAG_INT, RP2040_GPIO_INTR_EDGE_HIGH, + handler, arg); + if (err < 0) + { + return err; + } + + rp2040_gpio_enable_irq(GPIO_MAG_INT); + return err; + } + + /* Later, in the board bringup code ... */ + + int err; + err = lis2mdl_register(i2c_master, 0, 0x1e, board_lis2mdl_attach); + if (err < 0) + { + syslog(LOG_ERR, "Couldn't register LIS2MDL driver: %d\n", err); + } + +To debug this device, you can include the ``uorb_listener`` in your build with +debugging enabled. Running it will show the sensor measurements. + +.. warning:: + By default, when the sensor is deactivated via the UORB interface, it is put + into low power mode and set to idle. When it is reactivated, it is put in + high resolution mode and set to continuous measurement. If you want to + measure continuously in low power mode, you will need to use the + ``SNIOC_SET_POWER_MODE`` command explained below. + +The ``set_calibvalue`` interface to this sensor takes an array of three `float` +types, representing hard-iron offsets in micro Teslas. This offset is set on the +sensor and is subtracted from measurements to compensate for environmental +effects. + +Some additional control commands for the LIS2MDL are listed below. + +``SNIOC_WHO_AM_I`` +------------------ + +This command reads the ``WHOAMI`` register of the LIS2MDL. This should always +return ``0x40``. The argument is a pointer to an 8-bit unsigned integer. + +.. code-block:: c + + uint8_t id; /* Should always contain 0x40 */ + err = orb_ioctl(sensor, SNIOC_WHO_AM_I, &id); + +``SNIOC_SET_POWER_MODE`` +------------------------ + +This command selects the power mode of the LIS2MDL sensor. An argument of +``true`` puts the sensor in low power mode, and ``false`` puts the sensor into +high resolution mode. + +.. code-block:: c + + /* Puts LIS2MDL into low power mode */ + err = orb_ioctl(sensor, SNIOC_WHO_AM_I, true); + +``SNIOC_RESET`` +---------------- + +Performs a soft reset of the LIS2MDL, which resets the user registers. This +command takes no arguments. Once this command is issue, 5 microseconds must pass +before the sensor is operational again. + +.. code-block:: c + + err = orb_ioctl(sensor, SNIOC_RESET, NULL); + +``SNIOC_SENSOR_OFF`` +-------------------- + +Performs a reboot of the LIS2MDL's memory contents. This command takes no +arguments. After the command is issued, 20ms must pass before the sensor is +operational again. + +.. code-block:: c + + err = orb_ioctl(sensor, SNIOC_SENSOR_OFF, NULL); + +``SNIOC_SET_TEMP_OFFSET`` +-------------------------- + +Enables or disables temperature compensation on the magnetometer. A arg of +``true`` enables compensation, ``false`` disables it. By default this is +enabled. + +.. code-block:: c + + err = orb_ioctl(sensor, SNIOC_SET_TEMP_OFFSET, true); + +``SNIOC_LPF`` +------------- + +Enables or disables the magnetometer low pass filter. A arg of ``true`` enables +the filter, ``false`` disables it. By default this is disabled. + +.. code-block:: c + + err = orb_ioctl(sensor, SNIOC_LPF, true); diff --git a/Documentation/components/drivers/special/sensors/sensors_legacy.rst b/Documentation/components/drivers/special/sensors/sensors_legacy.rst index f578fbe11a5..a64a8e9faa0 100644 --- a/Documentation/components/drivers/special/sensors/sensors_legacy.rst +++ b/Documentation/components/drivers/special/sensors/sensors_legacy.rst @@ -72,7 +72,6 @@ Drivers that are available also with the new sensor framework are marked with `` - sgp30 - sht21 - sht3x -- :doc:`sht4x` - sps30 - t67xx - veml6070 diff --git a/Documentation/components/drivers/special/sensors/sensors_uorb.rst b/Documentation/components/drivers/special/sensors/sensors_uorb.rst index 01006c8ddd8..7f470548a18 100644 --- a/Documentation/components/drivers/special/sensors/sensors_uorb.rst +++ b/Documentation/components/drivers/special/sensors/sensors_uorb.rst @@ -484,8 +484,10 @@ Implemented Drivers - goldfish_sensor - hyt271 - l3gd20 +- :doc:`lis2mdl` - lsm9ds1 - ltr308 - mpu9250 - ms56xx +- :doc:`sht4x` - wtgahrs2 diff --git a/drivers/sensors/CMakeLists.txt b/drivers/sensors/CMakeLists.txt index 02cfd2579b6..8df46b13a4f 100644 --- a/drivers/sensors/CMakeLists.txt +++ b/drivers/sensors/CMakeLists.txt @@ -248,6 +248,10 @@ if(CONFIG_SENSORS) list(APPEND SRCS ltc4151.c) endif() + if(CONFIG_SENSORS_LIS2MDL) + list(APPEND SRCS lis2mdl_uorb.c) + endif() + if(CONFIG_SENSORS_INA219) list(APPEND SRCS ina219.c) endif() diff --git a/drivers/sensors/Kconfig b/drivers/sensors/Kconfig index 4bf75df32e6..5b07a9f5798 100644 --- a/drivers/sensors/Kconfig +++ b/drivers/sensors/Kconfig @@ -1436,6 +1436,34 @@ config SENSORS_MAX6675 ---help--- Enables support for the MAX6675 driver +config SENSORS_LIS2MDL + bool "STMicro LIS2MDL 3-Axis magnetometer support" + default n + select I2C + ---help--- + Enable UORB driver support for the STMicro LIS2MDL 3-axis magnetometer. + +if SENSORS_LIS2MDL + +config SENSORS_LIS2MDL_FREQUENCY + int "LIS2MDL I2C frequency" + default 1000000 + range 1 3400000 + +config SENSORS_LIS2MDL_THREAD_STACKSIZE + int "LIS2MDL worker thread stack size" + default 1024 + ---help--- + The stack size for the worker thread that performs measurements + +config SENSORS_LIS2MDL_FETCH + bool "Enable LIS2MDL fetch capability" + default n + ---help--- + Enables the `fetch` UORB function for the LIS2MDL + +endif + config SENSORS_LIS3MDL bool "STMicro LIS3MDL 3-Axis magnetometer support" default n diff --git a/drivers/sensors/Make.defs b/drivers/sensors/Make.defs index 95410290da2..3e7bbfdcdbd 100644 --- a/drivers/sensors/Make.defs +++ b/drivers/sensors/Make.defs @@ -208,6 +208,10 @@ ifeq ($(CONFIG_SENSORS_HTS221),y) CSRCS += hts221.c endif +ifeq ($(CONFIG_SENSORS_LIS2MDL),y) + CSRCS += lis2mdl_uorb.c +endif + ifeq ($(CONFIG_LM75_I2C),y) CSRCS += lm75.c endif diff --git a/drivers/sensors/lis2mdl_uorb.c b/drivers/sensors/lis2mdl_uorb.c new file mode 100644 index 00000000000..eb705e2fd18 --- /dev/null +++ b/drivers/sensors/lis2mdl_uorb.c @@ -0,0 +1,1391 @@ +/**************************************************************************** + * drivers/sensors/lis2mdl_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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* The LIS2MDL supports up to 3.4MHz bus speeds */ + +#ifndef CONFIG_SENSORS_LIS2MDL_I2C_FREQUENCY +#define CONFIG_SENSORS_LIS2MDL_I2C_FREQUENCY 1000000 +#endif + +/* The value that should be in the "who am I" register */ + +#define WHO_AM_I_VAL 0x40 + +/* Registers */ + +#define REG_OFFSET_X_REG_L 0x45 +#define REG_OFFSET_X_REG_H 0x46 +#define REG_OFFSET_Y_REG_L 0x47 +#define REG_OFFSET_Y_REG_H 0x48 +#define REG_OFFSET_Z_REG_L 0x49 +#define REG_OFFSET_Z_REG_H 0x4a +#define REG_WHO_AM_I 0x4f +#define REG_CFG_REG_A 0x60 +#define REG_CFG_REG_B 0x61 +#define REG_CFG_REG_C 0x62 +#define REG_INT_CRTL_REG 0x63 +#define REG_INT_SOURCE_REG 0x64 +#define REG_INT_THS_L_REG 0x65 +#define REG_INT_THS_H_REG 0x66 +#define REG_STATUS_REG 0x67 +#define REG_OUTX_L_REG 0x68 +#define REG_OUTX_H_REG 0x69 +#define REG_OUTY_L_REG 0x6a +#define REG_OUTY_H_REG 0x6b +#define REG_OUTZ_L_REG 0x6c +#define REG_OUTZ_H_REG 0x6d +#define REG_TEMP_OUT_L_REG 0x6e +#define REG_TEMP_OUT_H_REG 0x6f + +/* Important register bits */ + +#define BIT_LOWPWR 4 /* Low power mode */ +#define BIT_SOFT_RST 5 /* Soft reset */ +#define BIT_REBOOT 6 /* Memory reboot */ +#define BIT_TEMP_COMP 7 /* Temperature compensation */ +#define BIT_SELF_TEST 1 /* Self test enabled */ +#define BIT_ZYXDA 3 /* New XYZ data available */ +#define BIT_DRDY 0 /* Data ready signal enable */ +#define BIT_OFFSET_CANC 1 /* Offset cancellation enable */ +#define BIT_LPF 0 /* Low pass filter enable */ + +/* Unit conversion of Gauss to micro Tesla */ + +#define GAUSS_TO_UTESLA 100.0f + +/* Sensitivity of magnetometer (1.5 milligauss/LSB) */ + +#define MAG_SENSITIVITY 0.0015f + +/* The number of samples required during a self-test */ + +#define SELFTEST_SAMPLES 50 + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* Valid ODR settings */ + +enum lis2mdl_odr_e +{ + ODR_10HZ = 0x0, + ODR_20HZ = 0x1, + ODR_50HZ = 0x2, + ODR_100HZ = 0x3, +}; + +/* Valid mode settings */ + +enum lis2mdl_mode_e +{ + MODE_CONT = 0x0, /* Continuous measurement */ + MODE_SINGLE = 0x1, /* Single measurement, then idle */ + MODE_IDLE = 0x2, /* Idle */ +}; + +/* Represents the main LIS2MDL device with a lower half for each data type */ + +struct lis2mdl_dev_s +{ + struct sensor_lowerhalf_s lower; /* UORB lower-half */ + FAR struct i2c_master_s *i2c; /* I2C interface */ + uint8_t addr; /* I2C address */ + sem_t run; /* Polling cycle lock */ + mutex_t devlock; /* Exclusive access to device */ + enum lis2mdl_odr_e odr; /* Configured ODR */ + bool lowpower; /* True if in low-power mode */ + bool offsets; /* True if offset cancellations + * are enabled */ + bool enabled; /* Whether the sensor is + * enabled */ + bool interrupts; /* Whether or not + * interrupts are enabled */ + enum lis2mdl_mode_e mode; /* Operation mode */ + struct work_s work; /* Interrupt work queue + * structure */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int lis2mdl_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, bool enable); +static int lis2mdl_set_interval(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR uint32_t *period_us); +static int lis2mdl_selftest(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, unsigned long arg); +static int lis2mdl_get_info(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR struct sensor_device_info_s *info); +static int lis2mdl_control(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, int cmd, + unsigned long arg); +static int lis2mdl_set_calibvalue(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, unsigned long arg); +#ifdef CONFIG_SENSORS_LIS2MDL_FETCH +static int lis2mdl_fetch(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, FAR char *buffer, + size_t buflen); +#endif + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Convert ODR settings to intervals in microseconds */ + +static const uint32_t ODR_TO_INTERVAL[] = +{ + [ODR_10HZ] = 100000, + [ODR_20HZ] = 50000, + [ODR_50HZ] = 20000, + [ODR_100HZ] = 10000, +}; + +/* Current consumption from operating mode + * Indexed by: CURRENT_CONS[odr][lp][offsets]; + * Current values in mA. + */ + +static const float CURRENT_CONS[][2][2] = +{ + [ODR_10HZ] = + { + {0.1f, 0.12f}, + {0.025f, 0.05f}, + }, + [ODR_20HZ] = + { + {0.2f, 0.235f}, + {0.05f, 0.1f}, + }, + [ODR_50HZ] = + { + {0.475f, 0.575f}, + {0.125f, 0.235f}, + }, + [ODR_100HZ] = + { + {0.95f, 1.13f}, + {0.25f, 0.46f}, + }, +}; + +/* Sensor operations */ + +static const struct sensor_ops_s g_sensor_ops = +{ + .activate = lis2mdl_activate, + .set_interval = lis2mdl_set_interval, + .selftest = lis2mdl_selftest, + .get_info = lis2mdl_get_info, + .control = lis2mdl_control, + .set_calibvalue = lis2mdl_set_calibvalue, +#ifdef CONFIG_SENSORS_LIS2MDL_FETCH + .fetch = lis2mdl_fetch, +#endif +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lis2mdl_read_reg + * + * Description: + * Read `nbytes` from the register at `addr` into `buf`. + ****************************************************************************/ + +static int lis2mdl_read_reg(FAR struct lis2mdl_dev_s *dev, uint8_t addr, + void *buf, uint8_t nbytes) +{ + struct i2c_msg_s readcmd[2] = { + { + .frequency = CONFIG_SENSORS_LIS2MDL_I2C_FREQUENCY, + .addr = dev->addr, + .flags = I2C_M_NOSTOP, + .buffer = &addr, + .length = sizeof(addr), + }, + { + .frequency = CONFIG_SENSORS_LIS2MDL_I2C_FREQUENCY, + .addr = dev->addr, + .flags = I2C_M_READ, + .buffer = buf, + .length = nbytes, + }, + }; + + return I2C_TRANSFER(dev->i2c, readcmd, 2); +} + +/**************************************************************************** + * Name: lis2mdl_write_reg + * + * Description: + * Write `nbytes` from `buf` to the registers starting at `addr`. + ****************************************************************************/ + +static int lis2mdl_write_reg(FAR struct lis2mdl_dev_s *dev, uint8_t addr, + void *buf, uint8_t nbytes) +{ + struct i2c_msg_s writecmd[2] = { + { + .frequency = CONFIG_SENSORS_LIS2MDL_I2C_FREQUENCY, + .addr = dev->addr, + .flags = I2C_M_NOSTOP, + .buffer = &addr, + .length = sizeof(addr), + }, + { + .frequency = CONFIG_SENSORS_LIS2MDL_I2C_FREQUENCY, + .addr = dev->addr, + .flags = I2C_M_NOSTART, + .buffer = buf, + .length = nbytes, + }, + }; + + return I2C_TRANSFER(dev->i2c, writecmd, 2); +} + +/**************************************************************************** + * Name: lis2mdl_read_data + * + * Description: + * Read temperature & mag data from the LIS2MDL into the mag struct. + ****************************************************************************/ + +static int lis2mdl_read_data(FAR struct lis2mdl_dev_s *dev, + FAR struct sensor_mag *data) +{ + int16_t raw_data[4]; /* X, Y, Z, temperature */ + int err; + + /* Read magnetometer axes followed by temperature */ + + err = lis2mdl_read_reg(dev, REG_OUTX_L_REG, raw_data, sizeof(raw_data)); + if (err < 0) + { + return err; + } + + data->timestamp = sensor_get_timestamp(); + data->temperature = ((float)(raw_data[3]) / 256.0f) + 25.0f; + data->x = (float)(raw_data[0]) * MAG_SENSITIVITY * GAUSS_TO_UTESLA; + data->y = (float)(raw_data[1]) * MAG_SENSITIVITY * GAUSS_TO_UTESLA; + data->z = (float)(raw_data[2]) * MAG_SENSITIVITY * GAUSS_TO_UTESLA; + data->status = 0; /* TODO what is this for? */ + + /* It is possible to check status register to see if there even is new + * data and return an error if there is not, but I have opted to avoid + * this check. Such a check requires extra overhead in the form of slow + * I2C communication. Plus, in interrupt-driven mode, this function should + * only be called when there has been an interrupt (or when doing + * driver-internal sampling for a self-test). + */ + + return err; +} + +/**************************************************************************** + * Name: lis2mdl_push_data + * + * Description: + * Reads some data with exclusive device access and pushed it to the UORB + * upper half. + ****************************************************************************/ + +static int lis2mdl_push_data(FAR struct lis2mdl_dev_s *dev) +{ + int err; + struct sensor_mag data; + + err = nxmutex_lock(&dev->devlock); + if (err < 0) + { + return err; + } + + if (!dev->enabled) + { + err = -EAGAIN; + goto unlock_ret; + } + + err = lis2mdl_read_data(dev, &data); + if (err < 0) + { + goto unlock_ret; + } + + dev->lower.push_event(dev->lower.priv, &data, sizeof(data)); + +unlock_ret: + nxmutex_unlock(&dev->devlock); + return err; +} + +/**************************************************************************** + * Name: lis2mdl_set_bit + * + * Description: + * Set or clear bit number `bit` of register `addr`, depending on `val`. + * + * Arguments: + * addr - The address of the register to modify + * bit - The bit number to set/clear + * val - True to set the bit, false to clear it + ****************************************************************************/ + +static int lis2mdl_set_bit(FAR struct lis2mdl_dev_s *dev, uint8_t addr, + uint8_t bit, bool val) +{ + int err; + uint8_t regval; + + /* Get the current value of the configuration register */ + + err = lis2mdl_read_reg(dev, addr, ®val, sizeof(regval)); + if (err < 0) + { + return err; + } + + /* Set or clear the bit */ + + regval &= ~(1 << bit); + + if (val) + { + regval |= (1 << bit); + } + + return lis2mdl_write_reg(dev, addr, ®val, sizeof(regval)); +} + +/**************************************************************************** + * Name: lis2mdl_set_odr + * + * Description: + * Set the output data rate of the LIS2MDL. + ****************************************************************************/ + +static int lis2mdl_set_odr(FAR struct lis2mdl_dev_s *dev, + enum lis2mdl_odr_e odr) +{ + int err; + uint8_t regval; + + /* Get the current value of the configuration register */ + + err = lis2mdl_read_reg(dev, REG_CFG_REG_A, ®val, sizeof(regval)); + if (err < 0) + { + return err; + } + + /* Add in the bits for ODR */ + + regval &= (0xf3); /* Clear ODR bits */ + regval |= (((uint8_t)(odr) & 0x3) << 2); /* Set ODR bits */ + + return lis2mdl_write_reg(dev, REG_CFG_REG_A, ®val, sizeof(regval)); +} + +/**************************************************************************** + * Name: lis2mdl_set_mode + * + * Description: + * Set the operation mode of the LIS2MDL. + ****************************************************************************/ + +static int lis2mdl_set_mode(FAR struct lis2mdl_dev_s *dev, + enum lis2mdl_mode_e mode) +{ + int err; + uint8_t regval; + + /* Get the current value of the configuration register */ + + err = lis2mdl_read_reg(dev, REG_CFG_REG_A, ®val, sizeof(regval)); + if (err < 0) + { + return err; + } + + /* Add in the bits for mode */ + + regval &= (~0x3); /* Clear mode bits */ + regval |= ((uint8_t)(mode) & 0x3); /* Set mode bits */ + + return lis2mdl_write_reg(dev, REG_CFG_REG_A, ®val, sizeof(regval)); +} + +/**************************************************************************** + * Name: lis2mdl_low_power + * + * Description: + * Put the LIS2MDL sensor in low power mode or high resolution mode. + * + * Arguments: + * lowpower - True to enter low power mode, false otherwise. + ****************************************************************************/ + +static int lis2mdl_low_power(FAR struct lis2mdl_dev_s *dev, bool lowpower) +{ + int err; + + /* Don't send request if low power mode already matches request */ + + if (dev->lowpower == lowpower) + { + return 0; + } + + err = lis2mdl_set_bit(dev, REG_CFG_REG_A, BIT_LOWPWR, lowpower); + if (err < 0) + { + return err; + } + + dev->lowpower = lowpower; + return err; +} + +/**************************************************************************** + * Name: lis2mdl_offset_enable + * + * Description: + * Enable offset corrections on the LIS2MDL. + * + * Arguments: + * enable - True to enable offsets, false otherwise. + ****************************************************************************/ + +static int lis2mdl_offset_enable(FAR struct lis2mdl_dev_s *dev, bool enable) +{ + int err; + + /* Don't send request if offset enable already matches request */ + + if (enable == dev->offsets) + { + return 0; + } + + err = lis2mdl_set_bit(dev, REG_CFG_REG_B, BIT_OFFSET_CANC, enable); + if (err < 0) + { + return err; + } + + dev->offsets = enable; + return err; +} + +/**************************************************************************** + * Name: lis2mdl_enable_interrupts + * + * Description: + * Enable offset corrections on the LIS2MDL. + * + * Arguments: + * enable - True to enable offsets, false otherwise. + ****************************************************************************/ + +static int lis2mdl_enable_interrupts(FAR struct lis2mdl_dev_s *dev, + bool enable) +{ + int err; + + /* Don't send request if request already matches current state */ + + if (enable == dev->interrupts) + { + return 0; + } + + err = lis2mdl_set_bit(dev, REG_CFG_REG_C, BIT_DRDY, enable); + if (err < 0) + { + return err; + } + + dev->interrupts = enable; + return err; +} + +/**************************************************************************** + * Name: lis2mdl_temp_compensation + * + * Description: + * Enable/disable magnetometer temperature compensation. + * + * Arguments: + * enable - True to enable temperature compensation, false otherwise + ****************************************************************************/ + +static int lis2mdl_temp_compensation(FAR struct lis2mdl_dev_s *dev, + bool enable) +{ + return lis2mdl_set_bit(dev, REG_CFG_REG_A, BIT_TEMP_COMP, enable); +} + +/**************************************************************************** + * Name: lis2mdl_enable_lpf + * + * Description: + * Enable/disable magnetometer low pass filter. + * + * Arguments: + * enable - True to enable the low pass filter, false otherwise + ****************************************************************************/ + +static int lis2mdl_enable_lpf(FAR struct lis2mdl_dev_s *dev, bool enable) +{ + return lis2mdl_set_bit(dev, REG_CFG_REG_B, BIT_LPF, enable); +} + +/**************************************************************************** + * Name: lis2mdl_reboot + * + * Description: + * Reboot the sensor. + ****************************************************************************/ + +static int lis2mdl_reboot(FAR struct lis2mdl_dev_s *dev) +{ + return lis2mdl_set_bit(dev, REG_CFG_REG_A, BIT_REBOOT, true); +} + +/**************************************************************************** + * Name: lis2mdl_reset + * + * Description: + * Perform a soft reset of the sensor. + ****************************************************************************/ + +static int lis2mdl_reset(FAR struct lis2mdl_dev_s *dev) +{ + return lis2mdl_set_bit(dev, REG_CFG_REG_A, BIT_SOFT_RST, true); +} + +/**************************************************************************** + * Name: lis2mdl_data_avail + * + * Description: + * Check if data is available. If yes, return 0. If no, return -EAGAIN. + ****************************************************************************/ + +static int lis2mdl_data_avail(FAR struct lis2mdl_dev_s *dev) +{ + int err; + uint8_t regval; + + /* Get the current value of the status register */ + + err = lis2mdl_read_reg(dev, REG_STATUS_REG, ®val, sizeof(regval)); + if (err < 0) + { + return err; + } + + /* Check if bit is set */ + + if (regval & (1 << BIT_ZYXDA)) + { + return 0; + } + + return -EAGAIN; +} + +/**************************************************************************** + * Name: lis2mdl_get_info + ****************************************************************************/ + +static int lis2mdl_get_info(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR struct sensor_device_info_s *info) +{ + FAR struct lis2mdl_dev_s *dev = + container_of(lower, FAR struct lis2mdl_dev_s, lower); + + DEBUGASSERT(dev != NULL); + + info->version = 0; + info->power = CURRENT_CONS[dev->odr][dev->lowpower][dev->offsets]; + memcpy(info->name, "LIS2MDL", sizeof("LIS2MDL")); + memcpy(info->name, "STMicro", sizeof("STMicro")); + + info->min_delay = ODR_TO_INTERVAL[ODR_10HZ]; + info->max_delay = ODR_TO_INTERVAL[ODR_10HZ]; + info->fifo_reserved_event_count = 0; + info->fifo_max_event_count = 0; + info->max_range = 49.152f * GAUSS_TO_UTESLA; /* +/- 49.152 gauss */ + info->resolution = MAG_SENSITIVITY * GAUSS_TO_UTESLA; + + return 0; +} + +/**************************************************************************** + * Name: lis2mdl_set_interval + ****************************************************************************/ + +static int lis2mdl_set_interval(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, + FAR uint32_t *period_us) +{ + FAR struct lis2mdl_dev_s *dev = + container_of(lower, FAR struct lis2mdl_dev_s, lower); + enum lis2mdl_odr_e odr; + int err = 0; + + DEBUGASSERT(dev != NULL); + + if (*period_us >= 100000) + { + /* 10Hz or lower, set 10Hz */ + + odr = ODR_10HZ; + } + else if (*period_us >= 50000 && *period_us < 100000) + { + /* 10 - 20Hz, set 20Hz */ + + odr = ODR_20HZ; + } + else if (*period_us >= 20000 && *period_us < 50000) + { + /* 20 - 50Hz, set 50Hz */ + + odr = ODR_50HZ; + } + else + { + /* >50Hz, set 100Hz */ + + odr = ODR_100HZ; + } + + err = lis2mdl_set_odr(dev, odr); + if (err < 0) + { + return err; + } + + *period_us = ODR_TO_INTERVAL[odr]; + dev->odr = odr; /* Record ODR */ + + return err; +} + +/**************************************************************************** + * Name: lis2mdl_activate + ****************************************************************************/ + +static int lis2mdl_activate(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, bool enable) +{ + FAR struct lis2mdl_dev_s *dev = + container_of(lower, FAR struct lis2mdl_dev_s, lower); + bool start_thread = false; + int err = 0; + + /* Start the collection thread if not already enabled */ + + if (enable && !dev->enabled) + { + start_thread = true; + + /* Enable temperature compensation (required for proper operation as + * per 8.5 in datasheet) + */ + + err = lis2mdl_temp_compensation(dev, true); + if (err < 0) + { + return err; + } + + /* Put the sensor in high resolution mode */ + + err = lis2mdl_low_power(dev, false); + if (err < 0) + { + return err; + } + + /* Start up the sensor for continuous measurement */ + + err = lis2mdl_set_mode(dev, MODE_CONT); + if (err < 0) + { + return err; + } + } + + /* Stop the sensor to save power if we're disabling */ + + if (!enable && dev->enabled) + { + err = lis2mdl_set_mode(dev, MODE_IDLE); + if (err < 0) + { + return err; + } + + /* Put the sensor in low power mode */ + + err = lis2mdl_low_power(dev, true); + if (err < 0) + { + return err; + } + } + + dev->enabled = enable; /* Mark state */ + + /* Start thread */ + + if (start_thread) + { + return nxsem_post(&dev->run); + } + + return 0; +} + +/**************************************************************************** + * Name: lis2mdl_selftest + * + * Description: + * Self test performed according to AN5069 from STMicro. + ****************************************************************************/ + +static int lis2mdl_selftest(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, unsigned long arg) +{ + FAR struct lis2mdl_dev_s *dev = + container_of(lower, FAR struct lis2mdl_dev_s, lower); + int err = 0; + enum lis2mdl_mode_e prev_mode = dev->mode; + bool prev_int = dev->interrupts; + struct sensor_mag data; + struct sensor_mag pre_avg; + struct sensor_mag post_avg; + + /* TODO: should I put this in a fast ODR just for the self-test? */ + + memset(&pre_avg, 0, sizeof(pre_avg)); + memset(&post_avg, 0, sizeof(post_avg)); + + sninfo("LIS2MDL begin selftest."); + + err = nxmutex_lock(&dev->devlock); + if (err < 0) + { + return err; + } + + /* Turn off interrupts for this self-test if they were enabled */ + + if (prev_int) + { + err = lis2mdl_enable_interrupts(dev, false); + if (err < 0) + { + goto end_test; + snerr("LIS2MDL failed to stop interrupts for selftest."); + } + } + + /* Enter continuous mode */ + + err = lis2mdl_set_mode(dev, MODE_CONT); + if (err < 0) + { + snerr("LIS2MDL failed to enter continuous mode for selftest."); + goto end_test; + } + + /* Perform samples without self-test enabled */ + + for (uint8_t i = 0; i < SELFTEST_SAMPLES; i++) + { + /* Check for data */ + + if (!lis2mdl_data_avail(dev)) + { + goto end_test; + } + + err = lis2mdl_read_data(dev, &data); + if (err < 0) + { + goto end_test; + } + + /* Add up samples */ + + pre_avg.x += data.x; + pre_avg.y += data.y; + pre_avg.z += data.z; + + /* Wait for the measurement interval */ + + nxsig_usleep(ODR_TO_INTERVAL[dev->odr]); + } + + sninfo("LIS2MDL regular samples complete."); + + /* Perform samples with self-test enabled */ + + err = lis2mdl_set_bit(dev, REG_CFG_REG_C, BIT_SELF_TEST, true); + if (err < 0) + { + snerr("LIS2MDL failed to enter self-test mode."); + goto end_test; + } + + sninfo("LIS2MDL waiting for self-test."); + nxsig_usleep(60000); /* Wait 60ms as per AN5069 */ + sninfo("LIS2MDL waiting for self-test over."); + + for (uint8_t i = 0; i < SELFTEST_SAMPLES; i++) + { + /* Check for data */ + + if (!lis2mdl_data_avail(dev)) + { + goto end_test; + } + + err = lis2mdl_read_data(dev, &data); + if (err < 0) + { + goto end_test; + } + + /* Add up samples */ + + post_avg.x += data.x; + post_avg.y += data.y; + post_avg.z += data.z; + + /* Wait for the measurement interval */ + + nxsig_usleep(ODR_TO_INTERVAL[dev->odr]); + } + + sninfo("LIS2MDL self-test samples complete."); + + /* Turn off self-test for normal operation */ + + err = lis2mdl_set_bit(dev, REG_CFG_REG_C, BIT_SELF_TEST, true); + if (err < 0) + { + goto end_test; + } + + /* Go back to previous operation mode */ + + err = lis2mdl_set_mode(dev, prev_mode); + if (err < 0) + { + goto end_test; + } + + /* Turn interrupts back on if they were enabled */ + + if (prev_int) + { + err = lis2mdl_enable_interrupts(dev, true); + if (err < 0) + { + snerr("LIS2MDL failed to re-enable interrupts."); + goto end_test; + } + } + + /* Average samples */ + + pre_avg.x /= SELFTEST_SAMPLES; + pre_avg.y /= SELFTEST_SAMPLES; + pre_avg.z /= SELFTEST_SAMPLES; + + post_avg.x /= SELFTEST_SAMPLES; + post_avg.y /= SELFTEST_SAMPLES; + post_avg.z /= SELFTEST_SAMPLES; + + /* Calculate diffs */ + + data.x = post_avg.x - pre_avg.x; + data.y = post_avg.y - pre_avg.y; + data.z = post_avg.z - pre_avg.z; + + /* Check if difference is within tolerance. */ + + if (data.x < 0.015f * GAUSS_TO_UTESLA || data.x > 0.5f * GAUSS_TO_UTESLA || + data.y < 0.015f * GAUSS_TO_UTESLA || data.y > 0.5f * GAUSS_TO_UTESLA || + data.z < 0.015f * GAUSS_TO_UTESLA || data.z > 0.5f * GAUSS_TO_UTESLA) + { + snerr("LIS2MDL failed self-test (%.2fX, %.2fY, %.2fZ)", data.x, data.y, + data.z); + err = -EAGAIN; + } + +end_test: + nxmutex_unlock(&dev->devlock); + sninfo("LIS2MDL selftest complete."); + return err; +} + +/**************************************************************************** + * Name: lis2mdl_set_calibvalue + * + * Description: + * Sets the hard-iron offset calibration values. Argument is three floats + * representing X, Y, Z offsets in micro-teslas (SI units) + ****************************************************************************/ + +static int lis2mdl_set_calibvalue(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, unsigned long arg) +{ + FAR struct lis2mdl_dev_s *dev = + container_of(lower, FAR struct lis2mdl_dev_s, lower); + int err = 0; + int16_t hardirons[3]; + + err = nxmutex_lock(&dev->devlock); + if (err < 0) + { + return err; + } + + /* Get user calibration values */ + + FAR float *vals = (float *)(arg); + if (vals == NULL) + { + err = -EINVAL; + goto early_ret; + } + + /* Convert to offsets in 1.5 milligauss per LSB */ + + hardirons[0] = (int16_t)(vals[0] * (1.0f / GAUSS_TO_UTESLA) * 1000.0f); + hardirons[1] = (int16_t)(vals[1] * (1.0f / GAUSS_TO_UTESLA) * 1000.0f); + hardirons[2] = (int16_t)(vals[2] * (1.0f / GAUSS_TO_UTESLA) * 1000.0f); + + /* Set values */ + + err = lis2mdl_write_reg(dev, REG_OFFSET_X_REG_L, hardirons, + sizeof(hardirons)); + + if (err < 0) + { + goto early_ret; + } + + /* Enable offset cancellation */ + + err = lis2mdl_offset_enable(dev, true); + +early_ret: + nxmutex_unlock(&dev->devlock); + return err; +} + +/**************************************************************************** + * Name: lis2mdl_fetch + ****************************************************************************/ + +#ifdef CONFIG_SENSORS_LIS2MDL_FETCH +static int lis2mdl_fetch(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct lis2mdl_dev_s *dev = + container_of(lower, FAR struct lis2mdl_dev_s, lower); + return lis2mdl_push_data(dev); +} +#endif /* CONFIG_SENSORS_LIS2MDL_FETCH */ + +/**************************************************************************** + * Name: lis2mdl_control + ****************************************************************************/ + +static int lis2mdl_control(FAR struct sensor_lowerhalf_s *lower, + FAR struct file *filep, int cmd, + unsigned long arg) +{ + FAR struct lis2mdl_dev_s *dev = + container_of(lower, FAR struct lis2mdl_dev_s, lower); + int err = 0; + + err = nxmutex_lock(&dev->devlock); + if (err) + { + return err; + } + + switch (cmd) + { + /* Get the WHOAMI register value */ + + case SNIOC_WHO_AM_I: + { + uint8_t *id = ((uint8_t *)(arg)); + if (id == NULL) + { + err = -EINVAL; + break; + } + + err = lis2mdl_read_reg(dev, REG_WHO_AM_I, id, 1); + } + break; + + /* Set low power mode if `arg` is truthy */ + + case SNIOC_SET_POWER_MODE: + { + err = lis2mdl_low_power(dev, arg); + } + break; + + /* Soft reset */ + + case SNIOC_RESET: + { + err = lis2mdl_reset(dev); + } + break; + + /* Reboot memory contents */ + + case SNIOC_SENSOR_OFF: + { + err = lis2mdl_reboot(dev); + } + break; + + /* Enable/disable temperature compensation */ + + case SNIOC_SET_TEMP_OFFSET: + { + err = lis2mdl_temp_compensation(dev, arg); + } + break; + + case SNIOC_LPF: + { + err = lis2mdl_enable_lpf(dev, arg); + } + break; + + default: + err = -EINVAL; + snerr("Unknown command for LIS2MDL: lu\n", cmd); + break; + } + + nxmutex_unlock(&dev->devlock); + return err; +} + +/**************************************************************************** + * Name: lis2mdl_worker + * + * Description: + * High priority worker thread to read data upon an interrupt and push it + * to the UORB upper half. + ****************************************************************************/ + +static void lis2mdl_worker(FAR void *arg) +{ + FAR struct lis2mdl_dev_s *dev = (FAR struct lis2mdl_dev_s *)(arg); + DEBUGASSERT(dev != NULL); + lis2mdl_push_data(dev); +} + +/**************************************************************************** + * Name: lis2mdl_int_handler + * + * Description: + * Interrupt handler for the LIS2MDL. `arg` should contain a reference + * to the LIS2MDL device instance. + ****************************************************************************/ + +static int lis2mdl_int_handler(int irq, FAR void *context, FAR void *arg) +{ + FAR struct lis2mdl_dev_s *dev = (FAR struct lis2mdl_dev_s *)(arg); + int err; + (void)context; + + DEBUGASSERT(dev != NULL); + + /* Start the high priority worker thread */ + + err = work_queue(HPWORK, &dev->work, &lis2mdl_worker, dev, 0); + + if (err < 0) + { + snerr("Could not queue LIS2MDL work queue."); + } + + return err; +} + +/**************************************************************************** + * Name: lis2mdl_thread + * + * Description: + * Kernel thread to poll the LIS2MDL, if not configured with interrupt + * handling. + ****************************************************************************/ + +static int lis2mdl_thread(int argc, char **argv) +{ + FAR struct lis2mdl_dev_s *dev = + (FAR struct lis2mdl_dev_s *)((uintptr_t)strtoul(argv[1], NULL, 16)); + int err = 0; + + while (true) + { + /* If the sensor is disabled we wait indefinitely */ + + if (!dev->enabled) + { + err = nxsem_wait(&dev->run); + if (err < 0) + { + continue; + } + } + + /* If the sensor is enabled, grab some data */ + + err = lis2mdl_push_data(dev); + if (err < 0) + { + return err; + } + + /* Wait for next measurement cycle */ + + nxsig_usleep(ODR_TO_INTERVAL[dev->odr]); + } + + return err; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: lis2mdl_register + * + * Description: + * Register the LIS2MDL device as a UORB sensor. + * + * Input Parameters: + * i2c - An instance of the I2C interface to use to communicate with + * the LIS2MDL. + * addr - The I2C address of the LIS2MDL. Should always be 0x1e. + * devno - The device number to use for the topic (i.e. /dev/mag0) + * attach - A function which is called by this driver to attach the + * LIS2MDL interrupt handler to an IRQ. Pass NULL to operate + * in polling mode. This function should return 0 on succes + * and a negated error code otherwise. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int lis2mdl_register(FAR struct i2c_master_s *i2c, int devno, uint8_t addr, + lis2mdl_attach attach) +{ + FAR struct lis2mdl_dev_s *priv; + int err; + FAR char *argv[2]; + char arg1[32]; + + DEBUGASSERT(i2c != NULL); + DEBUGASSERT(addr == 0x1e); + + /* High priority work queue is required for interrupt driven mode */ + +#if !defined(CONFIG_SCHED_HPWORK) + if (attach != NULL) + { + snerr("LIS2MDL driver needs `CONFIG_SCHED_HPWORK` in order to be " + "interrupt driven."); + return -ENOSYS; + } +#endif + + /* Initialize the device structure */ + + priv = kmm_zalloc(sizeof(struct lis2mdl_dev_s)); + if (priv == NULL) + { + snerr("Failed to allocate LIS2MDL instance.\n"); + return -ENOMEM; + } + + memset(priv, 0, sizeof(struct lis2mdl_dev_s)); + + priv->i2c = i2c; + priv->addr = addr; + + err = nxmutex_init(&priv->devlock); + if (err < 0) + { + snerr("Failed to register LIS2MDL driver: %d\n", err); + goto del_mem; + } + + err = nxsem_init(&priv->run, 0, 0); + if (err < 0) + { + snerr("Failed to register LIS2MDL driver: %d\n", err); + goto del_mutex; + } + + /* Register UORB Sensor */ + + priv->lower.ops = &g_sensor_ops; + priv->lower.type = SENSOR_TYPE_MAGNETIC_FIELD; + priv->enabled = false; + priv->lowpower = false; + priv->offsets = false; + priv->interrupts = false; + priv->odr = ODR_10HZ; /* 10Hz (0.1s) default ODR */ + + err = sensor_register(&priv->lower, devno); + if (err < 0) + { + snerr("Failed to register LIS2MDL driver: %d\n", err); + goto del_sem; + } + + /* If attach function is NULL, we operate in polling mode using a kernel + * thread. Otherwise, we attach our interrupt handler and operate in + * interrupt driven mode. + */ + + if (attach != NULL) + { + /* Register interrupt handler */ + + err = attach(lis2mdl_int_handler, priv); + + if (err < 0) + { + snerr("Failed to attach LIS2MDL interrupt handler: %d\n", err); + goto sensor_unreg; + } + + /* Configure the LIS2MDL to output an interrupt when data is ready */ + + err = lis2mdl_enable_interrupts(priv, true); + if (err < 0) + { + snerr("Failed to enable LIS2MDL interrupts: %d\n", err); + goto sensor_unreg; + } + + sninfo("Registered LIS2MDL driver in interrupt driven mode.\n"); + } + else + { + /* Polling thread */ + + snprintf(arg1, 16, "%p", priv); + argv[0] = arg1; + argv[1] = NULL; + err = kthread_create("lis2mdl_thread", SCHED_PRIORITY_DEFAULT, + CONFIG_SENSORS_LIS2MDL_THREAD_STACKSIZE, + lis2mdl_thread, argv); + if (err < 0) + { + snerr("Failed to create the LIS2MDL notification kthread.\n"); + goto sensor_unreg; + } + + sninfo("Registered LIS2MDL driver with kernel polling thread.\n"); + } + + if (err < 0) + { + sensor_unreg: + sensor_unregister(&priv->lower, devno); + del_sem: + nxsem_destroy(&priv->run); + del_mutex: + nxmutex_destroy(&priv->devlock); + del_mem: + kmm_free(priv); + return err; + } + + sninfo("Registered LIS2MDL driver.\n"); + return err; +} diff --git a/include/nuttx/sensors/ioctl.h b/include/nuttx/sensors/ioctl.h index 96bf05dc745..7fdda71c08e 100644 --- a/include/nuttx/sensors/ioctl.h +++ b/include/nuttx/sensors/ioctl.h @@ -473,6 +473,13 @@ #define SNIOC_SET_THERMO _SNIOC(0x009F) +/* Command: SNIOC_LPF + * Description: Enable the low pass filter + * Argument: Boolean true for enable, false to disable. + */ + +#define SNIOC_LPF _SNIOC(0x00A0) + /**************************************************************************** * Public types ****************************************************************************/ diff --git a/include/nuttx/sensors/lis2mdl.h b/include/nuttx/sensors/lis2mdl.h new file mode 100644 index 00000000000..4701b80f0bf --- /dev/null +++ b/include/nuttx/sensors/lis2mdl.h @@ -0,0 +1,64 @@ +/**************************************************************************** + * include/nuttx/sensors/lis2mdl.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. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct i2c_master_s; /* Forward reference */ + +typedef int (*lis2mdl_attach)(xcpt_t, FAR void *arg); + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: lis2mdl_register + * + * Description: + * Register the LIS2MDL device as a UORB sensor. + * + * Input Parameters: + * i2c - An instance of the I2C interface to use to communicate with + * the LIS2MDL. + * addr - The I2C address of the LIS2MDL. Should always be 0x1e. + * devno - The device number to use for the topic (i.e. /dev/mag0) + * attach - A function which is called by this driver to attach the + * LIS2MDL interrupt handler to an IRQ. Pass NULL to operate + * in polling mode. This function should return 0 on succes + * and a negated error code otherwise. + * + * Returned Value: + * Zero (OK) on success; a negated errno value on failure. + * + ****************************************************************************/ + +int lis2mdl_register(FAR struct i2c_master_s *i2c, int devno, uint8_t addr, + lis2mdl_attach attach);