Commit 5d8834be authored by Mario Tesi's avatar Mario Tesi Committed by Maureen Helm
Browse files

driver: sensor: Add support for LIS2MDL Mag sensor

Added support to LIS2MDL Magnetometer sensor provided
with following features:
	- I2C interface
	- Mag data
	- Temperature data
	- ODR configurable by config or at runtime
	- Trigger mode selectable by menuconfig
	- IRQ pin configurable (by dts or Kconfig)
	- Hard Iron offset setting at runtime
	- Include yaml file

Tested on ST MEMS IKS01A2 + NUCLEO STM32F411RE board.
LIS2MDL connected to I2C master interface (SPI 3 wire not
supported yet).
Test run with all ODR {10, 20, 50, 100} Hz in poll and
trigger mode.

GPIO IRQ dts configuration has been tested by adding
to boards/arm/nucleo_f411re/nucleo_f411re.dts file
this patch:

&i2c1 {
        status = "ok";
        clock-frequency = <I2C_BITRATE_FAST>;
+
+       /* ST Microelectronics LIS2MDL mag sensor */
+       lis2mdl-magn@1e {
+               compatible = "st,lis2mdl-magn";
+               reg = <0x1e>;
+               irq-gpios = <&gpioa 4 0>;
+               label = "LIS2MDL";
+               status = "ok";
+       };
};

and adding boards/arm/nucleo_f411re/dts.fixup with following
content:

	ST_STM32_I2C_V1_40005400_ST_LIS2MDL_MAGN_1E_LABEL
	ST_STM32_I2C_V1_40005400_ST_LIS2MDL_MAGN_1E_BASE_ADDRESS
	ST_STM32_I2C_V1_40005400_ST_LIS2MDL_MAGN_1E_BUS_NAME
	ST_STM32_I2C_V1_40005400_ST_LIS2MDL_MAGN_1E_IRQ_GPIOS_CONTROLLER
	ST_STM32_I2C_V1_40005400_ST_LIS2MDL_MAGN_1E_IRQ_GPIOS_PIN

For more info on this LIS2MDL please follow this link:
http://www.st.com/en/mems-and-sensors/lis2mdl.html



Signed-off-by: default avatarMario Tesi <mario.tesi@st.com>
parent 3d72216b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ add_subdirectory_ifdef(CONFIG_HP206C hp206c)
add_subdirectory_ifdef(CONFIG_HTS221		hts221)
add_subdirectory_ifdef(CONFIG_ISL29035		isl29035)
add_subdirectory_ifdef(CONFIG_LIS2DH		lis2dh)
add_subdirectory_ifdef(CONFIG_LIS2MDL		lis2mdl)
add_subdirectory_ifdef(CONFIG_LIS3DH		lis3dh)
add_subdirectory_ifdef(CONFIG_LIS3MDL		lis3mdl)
add_subdirectory_ifdef(CONFIG_LPS22HB		lps22hb)
+2 −0
Original line number Diff line number Diff line
@@ -79,6 +79,8 @@ source "drivers/sensor/isl29035/Kconfig"

source "drivers/sensor/lis2dh/Kconfig"

source "drivers/sensor/lis2mdl/Kconfig"

source "drivers/sensor/lis3dh/Kconfig"

source "drivers/sensor/lis3mdl/Kconfig"
+9 −0
Original line number Diff line number Diff line
#
# Copyright (c) 2018 STMicroelectronics
#
# SPDX-License-Identifier: Apache-2.0
#

zephyr_library()
zephyr_library_sources_ifdef(CONFIG_LIS2MDL lis2mdl.c)
zephyr_library_sources_ifdef(CONFIG_LIS2MDL_TRIGGER lis2mdl_trigger.c)
+117 −0
Original line number Diff line number Diff line

# Copyright (c) 2018 STMicroelectronics
#
# SPDX-License-Identifier: Apache-2.0
#

menuconfig LIS2MDL
	bool "LIS2MDL Magnetometer"
	depends on I2C
	help
	  Enable driver for LIS2MDL I2C-based magnetometer sensor.

if LIS2MDL

config LIS2MDL_DEV_NAME
	string "Driver name"
	default "LIS2MDL"
	help
	  Device name with which the LIS2MDL sensor is identified.

if !HAS_DTS_I2C

config LIS2MDL_I2C_ADDR
	hex "I2C address"
	default 0x1E
	help
	  I2C address of the LIS2MDL sensor.

config LIS2MDL_I2C_MASTER_DEV_NAME
	string "I2C master where LIS2MDL is connected"
	default "I2C_0"
	help
	  Specify the device name of the I2C master device to which LIS2MDL is
	  connected.

endif # LIS2MDL_I2C_ADDR

choice
	prompt "Trigger mode"
	default LIS2MDL_TRIGGER_GLOBAL_THREAD
	help
	  Specify the type of triggering to be used by the driver.

config LIS2MDL_TRIGGER_NONE
	bool "No trigger"

config LIS2MDL_TRIGGER_GLOBAL_THREAD
	bool "Use global thread"
	depends on GPIO
	select LIS2MDL_TRIGGER

config LIS2MDL_TRIGGER_OWN_THREAD
	bool "Use own thread"
	depends on GPIO
	select LIS2MDL_TRIGGER

endchoice

config LIS2MDL_TRIGGER
	bool

if !HAS_DTS_GPIO

config LIS2MDL_GPIO_DEV_NAME
	string "GPIO device"
	default "GPIO_0"
	depends on LIS2MDL_TRIGGER
	help
	  The device name of the GPIO device to which the LIS2MDL interrupt pins
	  are connected.

config LIS2MDL_GPIO_PIN_NUM
	int "Interrupt GPIO pin number"
	default 0
	depends on LIS2MDL_TRIGGER
	help
	  The number of the GPIO on which the interrupt signal from the LIS2MDL
	  chip will be received.

endif # !HAS_DTS_GPIO

config LIS2MDL_THREAD_PRIORITY
	int "Thread priority"
	depends on LIS2MDL_TRIGGER_OWN_THREAD
	default 10
	help
	  Priority of thread used by the driver to handle interrupts.

config LIS2MDL_THREAD_STACK_SIZE
	int "Thread stack size"
	depends on LIS2MDL_TRIGGER_OWN_THREAD
	default 1024
	help
	  Stack size of thread used by the driver to handle interrupts.

choice
	prompt "Magnetometer sampling frequency (ODR)"
	default LIS2MDL_MAG_ODR_RUNTIME

config LIS2MDL_MAG_ODR_RUNTIME
	bool "Set ODR at runtime (default 10 Hz)"

config LIS2MDL_MAG_ODR_10
	bool "10 Hz"

config LIS2MDL_MAG_ODR_20
	bool "20 Hz"

config LIS2MDL_MAG_ODR_50
	bool "50 Hz"

config LIS2MDL_MAG_ODR_100
	bool "100 Hz"

endchoice

endif # LIS2MDL
+375 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2018 STMicroelectronics
 *
 * LIS2MDL mag driver
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <init.h>
#include <misc/__assert.h>
#include <misc/byteorder.h>
#include <sensor.h>
#include <string.h>

#include "lis2mdl.h"

struct lis2mdl_data lis2mdl_device_data;

#ifdef CONFIG_LIS2MDL_MAG_ODR_RUNTIME
static const struct {
	u16_t odr;
	u8_t regval;
	} lis2mdl_odr_reg[] = {
		{
			.odr = 10,
			.regval = LIS2MDL_ODR10_HZ,
		},
		{
			.odr = 20,
			.regval = LIS2MDL_ODR20_HZ,
		},
		{
			.odr = 50,
			.regval = LIS2MDL_ODR50_HZ,
		},
		{
			.odr = 100,
			.regval = LIS2MDL_ODR100_HZ,
		},
	};

static int lis2mdl_freq_to_odr_val(u16_t odr)
{
	size_t i;

	for (i = 0; i < ARRAY_SIZE(lis2mdl_odr_reg); i++) {
		if (odr == lis2mdl_odr_reg[i].odr) {
			return i;
		}
	}

	return -EINVAL;
}

static int lis2mdl_set_odr(struct device *dev, u16_t odr)
{
	int odr_idx;
	struct lis2mdl_data *lis2mdl = dev->driver_data;

	/* check if power off */
	if (odr == 0) {
		/* power off mag */
		return i2c_reg_update_byte(lis2mdl->i2c,
					   lis2mdl->i2c_addr,
					   LIS2MDL_CFG_REG_A,
					   LIS2MDL_MAG_MODE_MASK,
					   LIS2MDL_MD_IDLE1_MODE);
	}

	odr_idx = lis2mdl_freq_to_odr_val(odr);
	if (odr_idx < 0) {
		return odr_idx;
	}

	return i2c_reg_update_byte(lis2mdl->i2c,
				   lis2mdl->i2c_addr,
				   LIS2MDL_CFG_REG_A,
				   LIS2MDL_ODR_MASK,
				   lis2mdl_odr_reg[odr_idx].regval);
}
#endif /* CONFIG_LIS2MDL_MAG_ODR_RUNTIME */

static int lis2mdl_set_hard_iron(struct device *dev, enum sensor_channel chan,
				   const struct sensor_value *val)
{
	struct lis2mdl_data *lis2mdl = dev->driver_data;
	u8_t regs = LIS2MDL_OFFSET_X_REG_L;
	u8_t i;
	s16_t offs;
	int ret;

	for (i = 0; i < LIS2MDL_NUM_AXIS; i++) {
		offs = sys_cpu_to_le16(val->val1);
		ret = i2c_burst_write(lis2mdl->i2c, lis2mdl->i2c_addr,
				      regs, (u8_t *)&offs, sizeof(offs));
		if (ret < 0) {
			return ret;
		}

		regs += sizeof(offs);
		val++;
	}

	return 0;
}

static void lis2mdl_channel_get_mag(struct device *dev,
				      enum sensor_channel chan,
				      struct sensor_value *val)
{
	s32_t cval;
	int i;
	u8_t ofs_start, ofs_stop;
	struct lis2mdl_data *lis2mdl = dev->driver_data;
	struct sensor_value *pval = val;

	switch (chan) {
	case SENSOR_CHAN_MAGN_X:
		ofs_start = ofs_stop = 0;
		break;
	case SENSOR_CHAN_MAGN_Y:
		ofs_start = ofs_stop = 1;
		break;
	case SENSOR_CHAN_MAGN_Z:
		ofs_start = ofs_stop = 2;
		break;
	default:
		ofs_start = 0; ofs_stop = 2;
		break;
	}

	for (i = ofs_start; i <= ofs_stop; i++) {
		cval = lis2mdl->mag[i] * lis2mdl->mag_fs_sensitivity;
		pval->val1 = cval / 1000000;
		pval->val2 = cval % 1000000;
		pval++;
	}
}

/* read internal temperature */
static void lis2mdl_channel_get_temp(struct device *dev,
				       struct sensor_value *val)
{
	struct lis2mdl_data *drv_data = dev->driver_data;

	val->val1 = drv_data->temp_sample / 100;
	val->val2 = drv_data->temp_sample % 100;
}

static int lis2mdl_channel_get(struct device *dev, enum sensor_channel chan,
				 struct sensor_value *val)
{
	switch (chan) {
	case SENSOR_CHAN_MAGN_X:
	case SENSOR_CHAN_MAGN_Y:
	case SENSOR_CHAN_MAGN_Z:
	case SENSOR_CHAN_MAGN_XYZ:
		lis2mdl_channel_get_mag(dev, chan, val);
		break;
	case SENSOR_CHAN_DIE_TEMP:
		lis2mdl_channel_get_temp(dev, val);
		break;
	default:
		SYS_LOG_DBG("Channel not supported");
		return -ENOTSUP;
	}

	return 0;
}

static int lis2mdl_config(struct device *dev, enum sensor_channel chan,
			    enum sensor_attribute attr,
			    const struct sensor_value *val)
{
	switch (attr) {
#ifdef CONFIG_LIS2MDL_MAG_ODR_RUNTIME
	case SENSOR_ATTR_SAMPLING_FREQUENCY:
		return lis2mdl_set_odr(dev, val->val1);
#endif /* CONFIG_LIS2MDL_MAG_ODR_RUNTIME */
	case SENSOR_ATTR_OFFSET:
		return lis2mdl_set_hard_iron(dev, chan, val);
	default:
		SYS_LOG_DBG("Mag attribute not supported");
		return -ENOTSUP;
	}

	return 0;
}

static int lis2mdl_attr_set(struct device *dev,
			      enum sensor_channel chan,
			      enum sensor_attribute attr,
			      const struct sensor_value *val)
{
	switch (chan) {
	case SENSOR_CHAN_MAGN_X:
	case SENSOR_CHAN_MAGN_Y:
	case SENSOR_CHAN_MAGN_Z:
	case SENSOR_CHAN_MAGN_XYZ:
		return lis2mdl_config(dev, chan, attr, val);
	default:
		SYS_LOG_DBG("attr_set() not supported on %d channel", chan);
		return -ENOTSUP;
	}

	return 0;
}

static int lis2mdl_sample_fetch_mag(struct device *dev)
{
	struct lis2mdl_data *lis2mdl = dev->driver_data;

	union {
		u8_t raw[LIS2MDL_OUT_REG_SIZE];
		struct {
			s16_t m_axis[3];
		};
	} buf __aligned(2);

	/* fetch raw data sample */
	if (i2c_burst_read(lis2mdl->i2c, lis2mdl->i2c_addr,
			   LIS2MDL_OUT_REG, buf.raw,
			   sizeof(buf)) < 0) {
		SYS_LOG_DBG("Failed to fetch raw mag sample");
		return -EIO;
	}

	lis2mdl->mag[0] = sys_le16_to_cpu(buf.m_axis[0]);
	lis2mdl->mag[1] = sys_le16_to_cpu(buf.m_axis[1]);
	lis2mdl->mag[2] = sys_le16_to_cpu(buf.m_axis[2]);

	return 0;
}

static int lis2mdl_sample_fetch_temp(struct device *dev)
{
	struct lis2mdl_data *lis2mdl = dev->driver_data;
	u16_t temp_raw;
	s32_t temp;
	int ret;

	/* fetch raw temperature sample */
	ret = i2c_burst_read(lis2mdl->i2c, lis2mdl->i2c_addr,
			     LIS2MDL_TEMP_OUT_L_REG,
			     (u8_t *)&temp_raw, sizeof(temp_raw));
	if (ret < 0) {
		SYS_LOG_DBG("Failed to fetch raw temp sample");
		return -EIO;
	}

	/* formula is temp = 25 + (temp / 8) C */
	temp = (sys_le16_to_cpu(temp_raw) & 0x8FFF);
	lis2mdl->temp_sample = 2500 + (temp * 100) / 8;

	return 0;
}

static int lis2mdl_sample_fetch(struct device *dev, enum sensor_channel chan)
{
	switch (chan) {
	case SENSOR_CHAN_MAGN_X:
	case SENSOR_CHAN_MAGN_Y:
	case SENSOR_CHAN_MAGN_Z:
	case SENSOR_CHAN_MAGN_XYZ:
		lis2mdl_sample_fetch_mag(dev);
		break;
	case SENSOR_CHAN_DIE_TEMP:
		lis2mdl_sample_fetch_temp(dev);
		break;
	case SENSOR_CHAN_ALL:
		lis2mdl_sample_fetch_mag(dev);
		lis2mdl_sample_fetch_temp(dev);
		break;
	default:
		return -ENOTSUP;
	}

	return 0;
}

static const struct sensor_driver_api lis2mdl_driver_api = {
	.attr_set = lis2mdl_attr_set,
#if CONFIG_LIS2MDL_TRIGGER
	.trigger_set = lis2mdl_trigger_set,
#endif
	.sample_fetch = lis2mdl_sample_fetch,
	.channel_get = lis2mdl_channel_get,
};

static int lis2mdl_init_interface(struct device *dev)
{
	const struct lis2mdl_device_config *const config =
						dev->config->config_info;
	struct lis2mdl_data *lis2mdl = dev->driver_data;

	lis2mdl->i2c = device_get_binding(config->master_dev_name);
	if (!lis2mdl->i2c) {
		SYS_LOG_DBG("Could not get pointer to %s device",
			    config->master_dev_name);
		return -EINVAL;
	}

	lis2mdl->i2c_addr = config->i2c_addr_config;

	return 0;
}

static const struct lis2mdl_device_config lis2mdl_dev_config = {
	.master_dev_name = CONFIG_LIS2MDL_I2C_MASTER_DEV_NAME,
#ifdef CONFIG_LIS2MDL_TRIGGER
	.gpio_name = CONFIG_LIS2MDL_GPIO_DEV_NAME,
	.gpio_pin = CONFIG_LIS2MDL_GPIO_PIN_NUM,
#endif  /* CONFIG_LIS2MDL_TRIGGER */
	.i2c_addr_config = CONFIG_LIS2MDL_I2C_ADDR,
};

static int lis2mdl_init(struct device *dev)
{
	struct lis2mdl_data *lis2mdl = dev->driver_data;
	u8_t wai;

	if (lis2mdl_init_interface(dev)) {
		return -EINVAL;
	}

	/* check chip ID */
	if (i2c_reg_read_byte(lis2mdl->i2c, lis2mdl->i2c_addr,
			      LIS2MDL_WHO_AM_I_REG, &wai) < 0) {
		SYS_LOG_DBG("Failed to read chip ID");
		return -EIO;
	}

	if (wai != LIS2MDL_WHOAMI_VAL) {
		SYS_LOG_DBG("Invalid chip ID");
		return -EINVAL;
	}

	/* reset sensor configuration */
	if (i2c_reg_write_byte(lis2mdl->i2c, lis2mdl->i2c_addr,
			       LIS2MDL_CFG_REG_A, LIS2MDL_SOFT_RST)) {
		return -EIO;
	}

	k_busy_wait(100);

	/* enable BDU */
	if (i2c_reg_update_byte(lis2mdl->i2c, lis2mdl->i2c_addr,
				LIS2MDL_CFG_REG_C, LIS2MDL_BDU_MASK,
				LIS2MDL_BDU_BIT)) {
		return -EIO;
	}

	/* set continuous MODE in default ODR, enable temperature comp. */
	if (i2c_reg_write_byte(lis2mdl->i2c, lis2mdl->i2c_addr,
			       LIS2MDL_CFG_REG_A,
			       LIS2MDL_MD_CONT_MODE | LIS2MDL_DEFAULT_ODR |
			       LIS2MDL_COMP_TEMP_MASK)) {
		return -EIO;
	}

	lis2mdl->mag_fs_sensitivity = LIS2MDL_SENSITIVITY;

#ifdef CONFIG_LIS2MDL_TRIGGER
	if (lis2mdl_init_interrupt(dev) < 0) {
		SYS_LOG_DBG("Failed to initialize interrupts");
		return -EIO;
	}
#endif

	return 0;
}

DEVICE_AND_API_INIT(lis2mdl, CONFIG_LIS2MDL_DEV_NAME, lis2mdl_init,
		     &lis2mdl_device_data, &lis2mdl_dev_config, POST_KERNEL,
		     CONFIG_SENSOR_INIT_PRIORITY, &lis2mdl_driver_api);
Loading