drivers: sensor: Add suport for TMAG5170 3D Hall sensor

Introduce support for Texas Instruments TMAG5170
high-precision linear 3D Hall-effect SPI sensor.
This driver allows to configure measurements on
magnetic and temperature channels. It is also
possible to read rotation of the magnet.

Signed-off-by: Michal Morsisko <morsisko@gmail.com>
This commit is contained in:
Michal Morsisko 2023-08-09 21:34:05 +02:00 committed by Carles Cufí
commit 8e32b5ee0a
13 changed files with 1104 additions and 1 deletions

View file

@ -135,6 +135,7 @@ add_subdirectory_ifdef(CONFIG_TEMP_NRF5 nrf5)
add_subdirectory_ifdef(CONFIG_TH02 th02) add_subdirectory_ifdef(CONFIG_TH02 th02)
add_subdirectory_ifdef(CONFIG_TI_HDC ti_hdc) add_subdirectory_ifdef(CONFIG_TI_HDC ti_hdc)
add_subdirectory_ifdef(CONFIG_TI_HDC20XX ti_hdc20xx) add_subdirectory_ifdef(CONFIG_TI_HDC20XX ti_hdc20xx)
add_subdirectory_ifdef(CONFIG_TMAG5170 tmag5170)
add_subdirectory_ifdef(CONFIG_TMD2620 tmd2620) add_subdirectory_ifdef(CONFIG_TMD2620 tmd2620)
add_subdirectory_ifdef(CONFIG_TMP007 tmp007) add_subdirectory_ifdef(CONFIG_TMP007 tmp007)
add_subdirectory_ifdef(CONFIG_TMP108 tmp108) add_subdirectory_ifdef(CONFIG_TMP108 tmp108)

View file

@ -191,6 +191,7 @@ source "drivers/sensor/tcs3400/Kconfig"
source "drivers/sensor/th02/Kconfig" source "drivers/sensor/th02/Kconfig"
source "drivers/sensor/ti_hdc/Kconfig" source "drivers/sensor/ti_hdc/Kconfig"
source "drivers/sensor/ti_hdc20xx/Kconfig" source "drivers/sensor/ti_hdc20xx/Kconfig"
source "drivers/sensor/tmag5170/Kconfig"
source "drivers/sensor/tmd2620/Kconfig" source "drivers/sensor/tmd2620/Kconfig"
source "drivers/sensor/tmp007/Kconfig" source "drivers/sensor/tmp007/Kconfig"
source "drivers/sensor/tmp108/Kconfig" source "drivers/sensor/tmp108/Kconfig"

View file

@ -0,0 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(tmag5170.c)
zephyr_library_sources_ifdef(CONFIG_TMAG5170_TRIGGER tmag5170_trigger.c)

View file

@ -0,0 +1,69 @@
# Texas Instruments TMAG5170 high-precision, linear 3D Hall-effect sensor with SPI bus interface
# Copyright (c) 2023 Michal Morsisko
# SPDX-License-Identifier: Apache-2.0
menuconfig TMAG5170
bool "TMAG5170 SPI Hall-effect sensor driver"
default y
depends on DT_HAS_TI_TMAG5170_ENABLED
select SPI
help
Enable driver for TMAG5170 Hall-effect sensor driver
if TMAG5170
choice TMAG5170_TRIGGER_MODE
prompt "Trigger mode"
help
Specify the type of triggering to be used by the driver.
config TMAG5170_TRIGGER_NONE
bool "No trigger"
config TMAG5170_TRIGGER_GLOBAL_THREAD
bool "Use global thread"
depends on GPIO
select TMAG5170_TRIGGER
config TMAG5170_TRIGGER_OWN_THREAD
bool "Use own thread"
depends on GPIO
select TMAG5170_TRIGGER
config TMAG5170_TRIGGER_DIRECT
bool "Process trigger within interrupt context"
depends on GPIO
select TMAG5170_TRIGGER
endchoice
config TMAG5170_CRC
bool "Use CRC error detection"
default y
select CRC
help
Verify CRC of RX data and append CRC to TX data
config TMAG5170_TRIGGER
bool
if TMAG5170_TRIGGER
config TMAG5170_THREAD_PRIORITY
int "Thread priority"
depends on TMAG5170_TRIGGER_OWN_THREAD
default 10
help
Priority of thread used by the driver to handle interrupts.
config TMAG5170_THREAD_STACK_SIZE
int "Thread stack size"
depends on TMAG5170_TRIGGER_OWN_THREAD
default 1024
help
Stack size of thread used by the driver to handle interrupts.
endif # TMAG5170_TRIGGER
endif # TMAG5170

View file

@ -0,0 +1,586 @@
/*
* Copyright (c) 2023 Michal Morsisko
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_tmag5170
#include <zephyr/drivers/sensor.h>
#include <zephyr/logging/log.h>
#include <zephyr/kernel.h>
#include <zephyr/devicetree.h>
#include <zephyr/pm/device.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/__assert.h>
#if defined(CONFIG_TMAG5170_CRC)
#include <zephyr/sys/crc.h>
#endif
#include "tmag5170.h"
#define TMAG5170_REG_DEVICE_CONFIG 0x0U
#define TMAG5170_REG_SENSOR_CONFIG 0x1U
#define TMAG5170_REG_SYSTEM_CONFIG 0x2U
#define TMAG5170_REG_ALERT_CONFIG 0x3U
#define TMAG5170_REG_X_THRX_CONFIG 0x4U
#define TMAG5170_REG_Y_THRX_CONFIG 0x5U
#define TMAG5170_REG_Z_THRX_CONFIG 0x6U
#define TMAG5170_REG_T_THRX_CONFIG 0x7U
#define TMAG5170_REG_CONV_STATUS 0x8U
#define TMAG5170_REG_X_CH_RESULT 0x9U
#define TMAG5170_REG_Y_CH_RESULT 0xAU
#define TMAG5170_REG_Z_CH_RESULT 0xBU
#define TMAG5170_REG_TEMP_RESULT 0xCU
#define TMAG5170_REG_AFE_STATUS 0xDU
#define TMAG5170_REG_SYS_STATUS 0xEU
#define TMAG5170_REG_TEST_CONFIG 0xFU
#define TMAG5170_REG_OSC_MONITOR 0x10U
#define TMAG5170_REG_MAG_GAIN_CONFIG 0x11U
#define TMAG5170_REG_MAG_OFFSET_CONFIG 0x12U
#define TMAG5170_REG_ANGLE_RESULT 0x13U
#define TMAG5170_REG_MAGNITUDE_RESULT 0x14U
#define TMAG5170_CONV_AVG_POS 12U
#define TMAG5170_CONV_AVG_MASK (BIT_MASK(3U) << TMAG5170_CONV_AVG_POS)
#define TMAG5170_CONV_AVG_SET(value) (((value) << TMAG5170_CONV_AVG_POS) &\
TMAG5170_CONV_AVG_MASK)
#define TMAG5170_MAG_TEMPCO_POS 8U
#define TMAG5170_MAG_TEMPCO_MASK (BIT_MASK(2U) << TMAG5170_MAG_TEMPCO_POS)
#define TMAG5170_MAG_TEMPCO_SET(value) (((value) << TMAG5170_MAG_TEMPCO_POS) &\
TMAG5170_MAG_TEMPCO_MASK)
#define TMAG5170_OPERATING_MODE_POS 4U
#define TMAG5170_OPERATING_MODE_MASK (BIT_MASK(3U) << TMAG5170_OPERATING_MODE_POS)
#define TMAG5170_OPERATING_MODE_SET(value) (((value) << TMAG5170_OPERATING_MODE_POS) &\
TMAG5170_OPERATING_MODE_MASK)
#define TMAG5170_T_CH_EN_POS 3U
#define TMAG5170_T_CH_EN_MASK (BIT_MASK(1U) << TMAG5170_T_CH_EN_POS)
#define TMAG5170_T_CH_EN_SET(value) (((value) << TMAG5170_T_CH_EN_POS) &\
TMAG5170_T_CH_EN_MASK)
#define TMAG5170_T_RATE_POS 2U
#define TMAG5170_T_RATE_MASK (BIT_MASK(1U) << TMAG5170_T_RATE_POS)
#define TMAG5170_T_RATE_SET(value) (((value) << TMAG5170_T_RATE_POS) &\
TMAG5170_T_RATE_MASK)
#define TMAG5170_ANGLE_EN_POS 14U
#define TMAG5170_ANGLE_EN_MASK (BIT_MASK(2U) << TMAG5170_ANGLE_EN_POS)
#define TMAG5170_ANGLE_EN_SET(value) (((value) << TMAG5170_ANGLE_EN_POS) &\
TMAG5170_ANGLE_EN_MASK)
#define TMAG5170_SLEEPTIME_POS 10U
#define TMAG5170_SLEEPTIME_MASK (BIT_MASK(4U) << TMAG5170_SLEEPTIME_POS)
#define TMAG5170_SLEEPTIME_SET(value) (((value) << TMAG5170_SLEEPTIME_POS) &\
TMAG5170_SLEEPTIME_MASK)
#define TMAG5170_MAG_CH_EN_POS 6U
#define TMAG5170_MAG_CH_EN_MASK (BIT_MASK(4U) << TMAG5170_MAG_CH_EN_POS)
#define TMAG5170_MAG_CH_EN_SET(value) (((value) << TMAG5170_MAG_CH_EN_POS) &\
TMAG5170_MAG_CH_EN_MASK)
#define TMAG5170_Z_RANGE_POS 4U
#define TMAG5170_Z_RANGE_MASK (BIT_MASK(2U) << TMAG5170_Z_RANGE_POS)
#define TMAG5170_Z_RANGE_SET(value) (((value) << TMAG5170_Z_RANGE_POS) &\
TMAG5170_Z_RANGE_MASK)
#define TMAG5170_Y_RANGE_POS 2U
#define TMAG5170_Y_RANGE_MASK (BIT_MASK(2U) << TMAG5170_Y_RANGE_POS)
#define TMAG5170_Y_RANGE_SET(value) (((value) << TMAG5170_Y_RANGE_POS) &\
TMAG5170_Y_RANGE_MASK)
#define TMAG5170_X_RANGE_POS 0U
#define TMAG5170_X_RANGE_MASK (BIT_MASK(2U) << TMAG5170_X_RANGE_POS)
#define TMAG5170_X_RANGE_SET(value) (((value) << TMAG5170_X_RANGE_POS) &\
TMAG5170_X_RANGE_MASK)
#define TMAG5170_RSLT_ALRT_POS 8U
#define TMAG5170_RSLT_ALRT_MASK (BIT_MASK(1U) << TMAG5170_RSLT_ALRT_POS)
#define TMAG5170_RSLT_ALRT_SET(value) (((value) << TMAG5170_RSLT_ALRT_POS) &\
TMAG5170_RSLT_ALRT_MASK)
#define TMAG5170_VER_POS 4U
#define TMAG5170_VER_MASK (BIT_MASK(2U) << TMAG5170_VER_POS)
#define TMAG5170_VER_GET(value) (((value) & TMAG5170_VER_MASK) >> TMAG5170_VER_POS)
#define TMAG5170_A1_REV 0x0U
#define TMAG5170_A2_REV 0x1U
#define TMAG5170_MAX_RANGE_50MT_IDX 0x0U
#define TMAG5170_MAX_RANGE_25MT_IDX 0x1U
#define TMAG5170_MAX_RANGE_100MT_IDX 0x2U
#define TMAG5170_MAX_RANGE_EXTEND_FACTOR 0x3U
#define TMAG5170_CONFIGURATION_MODE 0x0U
#define TMAG5170_STAND_BY_MODE 0x1U
#define TMAG5170_ACTIVE_TRIGGER_MODE 0x3U
#define TMAG5170_SLEEP_MODE 0x5U
#define TMAG5170_DEEP_SLEEP_MODE 0x6U
#define TMAG5170_MT_TO_GAUSS_RATIO 10U
#define TMAG5170_T_SENS_T0 25U
#define TMAG5170_T_ADC_T0 17522U
#define TMAG5170_T_ADC_RES 60U
#define TMAG5170_CMD_TRIGGER_CONVERSION BIT(0U)
#define TMAG5170_CRC_SEED 0xFU
#define TMAG5170_CRC_POLY 0x3U
#define TMAG5170_SPI_BUFFER_LEN 4U
#define TMAG5170_SET_CRC(buf, crc) ((uint8_t *)(buf))[3] |= (crc & 0x0F)
#define TMAG5170_ZERO_CRC(buf) ((uint8_t *)(buf))[3] &= 0xF0
#define TMAG5170_GET_CRC(buf) ((uint8_t *)(buf))[3] & 0x0F
LOG_MODULE_REGISTER(TMAG5170, CONFIG_SENSOR_LOG_LEVEL);
static int tmag5170_transmit_raw(const struct tmag5170_dev_config *config,
uint8_t *buffer_tx,
uint8_t *buffer_rx)
{
const struct spi_buf tx_buf = {
.buf = buffer_tx,
.len = TMAG5170_SPI_BUFFER_LEN,
};
const struct spi_buf_set tx = {
.buffers = &tx_buf,
.count = 1
};
const struct spi_buf rx_buf = {
.buf = buffer_rx,
.len = TMAG5170_SPI_BUFFER_LEN,
};
const struct spi_buf_set rx = {
.buffers = &rx_buf,
.count = 1
};
int ret = spi_transceive_dt(&config->bus, &tx, &rx);
return ret;
}
static int tmag5170_transmit(const struct device *dev, uint8_t *buffer_tx, uint8_t *buffer_rx)
{
#if defined(CONFIG_TMAG5170_CRC)
TMAG5170_ZERO_CRC(buffer_tx);
uint8_t crc = crc4_ti(TMAG5170_CRC_SEED, buffer_tx, TMAG5170_SPI_BUFFER_LEN);
TMAG5170_SET_CRC(buffer_tx, crc);
#endif
int ret = tmag5170_transmit_raw(dev->config, buffer_tx, buffer_rx);
#if defined(CONFIG_TMAG5170_CRC)
if (buffer_rx != NULL && ret == 0) {
uint8_t read_crc = TMAG5170_GET_CRC(buffer_rx);
TMAG5170_ZERO_CRC(buffer_rx);
crc = crc4_ti(TMAG5170_CRC_SEED, buffer_rx, TMAG5170_SPI_BUFFER_LEN);
if (read_crc != crc) {
return -EIO;
}
}
#endif
return ret;
}
static int tmag5170_write_register(const struct device *dev, uint32_t reg, uint16_t data)
{
uint8_t buffer_tx[4] = { reg, (data >> 8) & 0xFF, data & 0xFF, 0x00U };
return tmag5170_transmit(dev, buffer_tx, NULL);
}
static int tmag5170_read_register(const struct device *dev,
uint32_t reg,
uint16_t *output,
uint8_t cmd)
{
uint8_t buffer_tx[4] = { BIT(7) | reg, 0x00U, 0x00U, (cmd & BIT_MASK(4U)) << 4U };
uint8_t buffer_rx[4] = { 0x00U };
int ret = tmag5170_transmit(dev, buffer_tx, buffer_rx);
*output = (buffer_rx[1] << 8) | buffer_rx[2];
return ret;
}
static int tmag5170_convert_magn_reading_to_gauss(struct sensor_value *output,
uint16_t chan_reading,
uint8_t chan_range,
uint8_t chip_revision)
{
uint16_t max_range_mt = 0U;
if (chan_range == TMAG5170_MAX_RANGE_50MT_IDX) {
max_range_mt = 50U;
} else if (chan_range == TMAG5170_MAX_RANGE_25MT_IDX) {
max_range_mt = 25U;
} else if (chan_range == TMAG5170_MAX_RANGE_100MT_IDX) {
max_range_mt = 100U;
} else {
return -ENOTSUP;
}
if (chip_revision == TMAG5170_A2_REV) {
max_range_mt *= TMAG5170_MAX_RANGE_EXTEND_FACTOR;
}
max_range_mt *= 2U;
/* The sensor returns data in mT, we need to convert it to Gauss */
uint32_t max_range_gauss = max_range_mt * TMAG5170_MT_TO_GAUSS_RATIO;
/* Convert from 2's complementary system */
int64_t result = chan_reading - ((chan_reading & 0x8000) << 1);
result *= max_range_gauss;
/* Scale to increase accuracy */
result *= 100000;
/* Divide as it is shown in datasheet */
result /= 65536;
/* Remove scale from the final result */
output->val1 = result / 100000;
output->val2 = result % 100000;
return 0;
}
static void tmag5170_convert_temp_reading_to_celsius(struct sensor_value *output,
uint16_t chan_reading)
{
int32_t result = chan_reading - TMAG5170_T_ADC_T0;
result = (TMAG5170_T_SENS_T0 * 100000) + (100000 * result / (int32_t)TMAG5170_T_ADC_RES);
output->val1 = result / 100000;
output->val2 = (result % 100000) * 10;
}
static void tmag5170_covert_angle_reading_to_degrees(struct sensor_value *output,
uint16_t chan_reading)
{
/* 12 MSBs store the integer part of the result,
* 4 LSBs store the fractional part of the result
*/
output->val1 = chan_reading >> 4;
output->val2 = ((chan_reading & 0xF) * 1000000) / 16;
}
static int tmag5170_sample_fetch(const struct device *dev,
enum sensor_channel chan)
{
const struct tmag5170_dev_config *cfg = dev->config;
struct tmag5170_data *drv_data = dev->data;
int ret = 0;
if (cfg->operating_mode == TMAG5170_STAND_BY_MODE ||
cfg->operating_mode == TMAG5170_ACTIVE_TRIGGER_MODE) {
uint16_t read_status;
tmag5170_read_register(dev,
TMAG5170_REG_SYS_STATUS,
&read_status,
TMAG5170_CMD_TRIGGER_CONVERSION);
/* Wait for the measurement to be ready.
* The waiting time will vary depending on the configuration
*/
k_sleep(K_MSEC(5U));
}
switch (chan) {
case SENSOR_CHAN_MAGN_X:
ret = tmag5170_read_register(dev, TMAG5170_REG_X_CH_RESULT, &drv_data->x, 0U);
break;
case SENSOR_CHAN_MAGN_Y:
ret = tmag5170_read_register(dev, TMAG5170_REG_Y_CH_RESULT, &drv_data->y, 0U);
break;
case SENSOR_CHAN_MAGN_Z:
ret = tmag5170_read_register(dev, TMAG5170_REG_Z_CH_RESULT, &drv_data->z, 0U);
break;
case SENSOR_CHAN_MAGN_XYZ:
ret = tmag5170_read_register(dev, TMAG5170_REG_X_CH_RESULT, &drv_data->x, 0U);
if (ret == 0) {
ret = tmag5170_read_register(dev,
TMAG5170_REG_Y_CH_RESULT,
&drv_data->y,
0U);
}
if (ret == 0) {
ret = tmag5170_read_register(dev,
TMAG5170_REG_Z_CH_RESULT,
&drv_data->z,
0U);
}
break;
case SENSOR_CHAN_ROTATION:
ret = tmag5170_read_register(dev,
TMAG5170_REG_ANGLE_RESULT,
&drv_data->angle,
0U);
break;
case SENSOR_CHAN_AMBIENT_TEMP:
ret = tmag5170_read_register(dev,
TMAG5170_REG_TEMP_RESULT,
&drv_data->temperature,
0U);
break;
case SENSOR_CHAN_ALL:
ret = tmag5170_read_register(dev,
TMAG5170_REG_TEMP_RESULT,
&drv_data->temperature,
0U);
if (ret == 0) {
ret = tmag5170_read_register(dev,
TMAG5170_REG_ANGLE_RESULT,
&drv_data->angle,
0U);
}
if (ret == 0) {
ret = tmag5170_read_register(dev,
TMAG5170_REG_X_CH_RESULT,
&drv_data->x,
0U);
}
if (ret == 0) {
ret = tmag5170_read_register(dev,
TMAG5170_REG_Y_CH_RESULT,
&drv_data->y,
0U);
}
if (ret == 0) {
ret = tmag5170_read_register(dev,
TMAG5170_REG_Z_CH_RESULT,
&drv_data->z,
0U);
}
break;
default:
ret = -ENOTSUP;
break;
}
return ret;
}
static int tmag5170_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
const struct tmag5170_dev_config *cfg = dev->config;
struct tmag5170_data *drv_data = dev->data;
int ret = 0;
switch (chan) {
case SENSOR_CHAN_MAGN_XYZ:
ret = tmag5170_convert_magn_reading_to_gauss(val,
drv_data->x,
cfg->x_range,
drv_data->chip_revision);
if (ret == 0) {
ret = tmag5170_convert_magn_reading_to_gauss(val + 1,
drv_data->y,
cfg->y_range,
drv_data->chip_revision);
}
if (ret == 0) {
ret = tmag5170_convert_magn_reading_to_gauss(val + 2,
drv_data->z,
cfg->z_range,
drv_data->chip_revision);
}
break;
case SENSOR_CHAN_MAGN_X:
ret = tmag5170_convert_magn_reading_to_gauss(val,
drv_data->x,
cfg->x_range,
drv_data->chip_revision);
break;
case SENSOR_CHAN_MAGN_Y:
ret = tmag5170_convert_magn_reading_to_gauss(val,
drv_data->y,
cfg->y_range,
drv_data->chip_revision);
break;
case SENSOR_CHAN_MAGN_Z:
ret = tmag5170_convert_magn_reading_to_gauss(val,
drv_data->z,
cfg->z_range,
drv_data->chip_revision);
break;
case SENSOR_CHAN_ROTATION:
tmag5170_covert_angle_reading_to_degrees(val, drv_data->angle);
break;
case SENSOR_CHAN_AMBIENT_TEMP:
tmag5170_convert_temp_reading_to_celsius(val, drv_data->temperature);
break;
default:
ret = -ENOTSUP;
break;
}
return ret;
}
static int tmag5170_init_registers(const struct device *dev)
{
const struct tmag5170_dev_config *cfg = dev->config;
struct tmag5170_data *drv_data = dev->data;
uint16_t test_cfg_reg = 0U;
int ret = 0;
#if !defined(CONFIG_TMAG5170_CRC)
const uint8_t disable_crc_packet[4] = { 0x0FU, 0x0U, 0x04U, 0x07U };
ret = tmag5170_transmit_raw(cfg, disable_crc_packet, NULL);
#endif
if (ret == 0) {
ret = tmag5170_read_register(dev, TMAG5170_REG_TEST_CONFIG, &test_cfg_reg, 0U);
}
if (ret == 0) {
drv_data->chip_revision = TMAG5170_VER_GET(test_cfg_reg);
ret = tmag5170_write_register(dev,
TMAG5170_REG_SENSOR_CONFIG,
TMAG5170_ANGLE_EN_SET(cfg->angle_measurement) |
TMAG5170_SLEEPTIME_SET(cfg->sleep_time) |
TMAG5170_MAG_CH_EN_SET(cfg->magnetic_channels) |
TMAG5170_Z_RANGE_SET(cfg->z_range) |
TMAG5170_Y_RANGE_SET(cfg->y_range) |
TMAG5170_X_RANGE_SET(cfg->x_range));
}
#if defined(CONFIG_TMAG5170_TRIGGER)
if (ret == 0) {
ret = tmag5170_write_register(dev,
TMAG5170_REG_ALERT_CONFIG,
TMAG5170_RSLT_ALRT_SET(1U));
}
#endif
if (ret == 0) {
ret = tmag5170_write_register(dev,
TMAG5170_REG_DEVICE_CONFIG,
TMAG5170_OPERATING_MODE_SET(cfg->operating_mode) |
TMAG5170_CONV_AVG_SET(cfg->oversampling) |
TMAG5170_MAG_TEMPCO_SET(cfg->magnet_type) |
TMAG5170_T_CH_EN_SET(cfg->tempeature_measurement) |
TMAG5170_T_RATE_SET(cfg->disable_temperature_oversampling));
}
return ret;
}
#ifdef CONFIG_PM_DEVICE
static int tmag5170_pm_action(const struct device *dev,
enum pm_device_action action)
{
int ret_val = 0;
switch (action) {
case PM_DEVICE_ACTION_RESUME:
tmag5170_write_register(dev,
TMAG5170_REG_DEVICE_CONFIG,
TMAG5170_OPERATING_MODE_SET(TMAG5170_CONFIGURATION_MODE));
/* As per datasheet, waking up from deep-sleep can take up to 500us */
k_sleep(K_USEC(500));
ret_val = tmag5170_init_registers(dev);
break;
case PM_DEVICE_ACTION_SUSPEND:
ret_val = tmag5170_write_register(dev,
TMAG5170_REG_DEVICE_CONFIG,
TMAG5170_OPERATING_MODE_SET(TMAG5170_DEEP_SLEEP_MODE));
break;
default:
ret_val = -ENOTSUP;
}
return ret_val;
}
#endif /* CONFIG_PM_DEVICE */
static const struct sensor_driver_api tmag5170_driver_api = {
.sample_fetch = tmag5170_sample_fetch,
.channel_get = tmag5170_channel_get,
#if defined(CONFIG_TMAG5170_TRIGGER)
.trigger_set = tmag5170_trigger_set
#endif
};
static int tmag5170_init(const struct device *dev)
{
const struct tmag5170_dev_config *cfg = dev->config;
int ret = 0;
if (!spi_is_ready_dt(&cfg->bus)) {
LOG_ERR("SPI dev %s not ready", cfg->bus.bus->name);
return -ENODEV;
}
ret = tmag5170_init_registers(dev);
if (ret != 0) {
return ret;
}
#if defined(CONFIG_TMAG5170_TRIGGER)
if (cfg->int_gpio.port) {
ret = tmag5170_trigger_init(dev);
}
#endif
return ret;
}
#define DEFINE_TMAG5170(_num) \
static struct tmag5170_data tmag5170_data_##_num; \
static const struct tmag5170_dev_config tmag5170_config_##_num = { \
.bus = SPI_DT_SPEC_INST_GET(_num, \
SPI_OP_MODE_MASTER | \
SPI_TRANSFER_MSB | \
SPI_WORD_SET(32), \
0), \
.magnetic_channels = DT_INST_ENUM_IDX(_num, magnetic_channels), \
.x_range = DT_INST_ENUM_IDX(_num, x_range), \
.y_range = DT_INST_ENUM_IDX(_num, y_range), \
.z_range = DT_INST_ENUM_IDX(_num, z_range), \
.operating_mode = DT_INST_PROP(_num, operating_mode), \
.oversampling = DT_INST_ENUM_IDX(_num, oversampling), \
.tempeature_measurement = DT_INST_PROP(_num, enable_temperature_channel), \
.magnet_type = DT_INST_ENUM_IDX(_num, magnet_type), \
.angle_measurement = DT_INST_ENUM_IDX(_num, angle_measurement), \
.disable_temperature_oversampling = DT_INST_PROP(_num, \
disable_temperature_oversampling), \
.sleep_time = DT_INST_ENUM_IDX(_num, sleep_time), \
IF_ENABLED(CONFIG_TMAG5170_TRIGGER, \
(.int_gpio = GPIO_DT_SPEC_INST_GET_OR(_num, int_gpios, { 0 }),)) \
}; \
PM_DEVICE_DT_INST_DEFINE(_num, tmag5170_pm_action); \
\
SENSOR_DEVICE_DT_INST_DEFINE(_num, \
tmag5170_init, \
PM_DEVICE_DT_INST_GET(_num), \
&tmag5170_data_##_num, \
&tmag5170_config_##_num, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&tmag5170_driver_api);
DT_INST_FOREACH_STATUS_OKAY(DEFINE_TMAG5170)

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2023 Michal Morsisko
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_DRIVERS_SENSOR_TMAG5170_TMAG5170_H_
#define ZEPHYR_DRIVERS_SENSOR_TMAG5170_TMAG5170_H_
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <zephyr/sys/util.h>
struct tmag5170_dev_config {
struct spi_dt_spec bus;
uint8_t magnetic_channels;
uint8_t x_range;
uint8_t y_range;
uint8_t z_range;
uint8_t oversampling;
bool tempeature_measurement;
uint8_t magnet_type;
uint8_t angle_measurement;
bool disable_temperature_oversampling;
uint8_t sleep_time;
uint8_t operating_mode;
#if defined(CONFIG_TMAG5170_TRIGGER)
struct gpio_dt_spec int_gpio;
#endif
};
struct tmag5170_data {
uint8_t chip_revision;
uint16_t x;
uint16_t y;
uint16_t z;
uint16_t temperature;
uint16_t angle;
#if defined(CONFIG_TMAG5170_TRIGGER)
struct gpio_callback gpio_cb;
sensor_trigger_handler_t handler_drdy;
const struct sensor_trigger *trigger_drdy;
const struct device *dev;
#endif
#if defined(CONFIG_TMAG5170_TRIGGER_OWN_THREAD)
struct k_sem sem;
struct k_thread thread;
K_THREAD_STACK_MEMBER(thread_stack,
CONFIG_TMAG5170_THREAD_STACK_SIZE);
#elif defined(CONFIG_TMAG5170_TRIGGER_GLOBAL_THREAD)
struct k_work work;
#endif
};
#if defined(CONFIG_TMAG5170_TRIGGER)
int tmag5170_trigger_set(const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
int tmag5170_trigger_init(const struct device *dev);
#endif
#endif /* ZEPHYR_DRIVERS_SENSOR_TMAG5170_TMAG5170_H_ */

View file

@ -0,0 +1,149 @@
/*
* Copyright (c) 2023 Michal Morsisko
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT ti_tmag5170
#include <zephyr/kernel.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/pm/device.h>
#include <zephyr/logging/log.h>
#include "tmag5170.h"
LOG_MODULE_DECLARE(TMAG5170, CONFIG_SENSOR_LOG_LEVEL);
static void tmag5170_handle_interrupts(const void *arg)
{
const struct device *dev = (const struct device *)arg;
struct tmag5170_data *data = dev->data;
if (data->handler_drdy) {
data->handler_drdy(dev, data->trigger_drdy);
}
}
#if defined(CONFIG_TMAG5170_TRIGGER_OWN_THREAD)
static void tmag5170_thread_main(void *arg1, void *unused1, void *unused2)
{
ARG_UNUSED(unused1);
ARG_UNUSED(unused2);
const struct device *dev = (const struct device *)arg1;
struct tmag5170_data *data = dev->data;
while (1) {
k_sem_take(&data->sem, K_FOREVER);
tmag5170_handle_interrupts(dev);
}
}
#endif
#if defined(CONFIG_TMAG5170_TRIGGER_GLOBAL_THREAD)
static void tmag5170_work_handler(struct k_work *work)
{
struct tmag5170_data *data = CONTAINER_OF(work,
struct tmag5170_data,
work);
tmag5170_handle_interrupts(data->dev);
}
#endif
static void tmag5170_gpio_callback(const struct device *port,
struct gpio_callback *cb,
uint32_t pin)
{
struct tmag5170_data *data = CONTAINER_OF(cb,
struct tmag5170_data,
gpio_cb);
ARG_UNUSED(port);
ARG_UNUSED(pin);
#if defined(CONFIG_TMAG5170_TRIGGER_OWN_THREAD)
k_sem_give(&data->sem);
#elif defined(CONFIG_TMAG5170_TRIGGER_GLOBAL_THREAD)
k_work_submit(&data->work);
#elif defined(CONFIG_TMAG5170_TRIGGER_DIRECT)
tmag5170_handle_interrupts(data->dev);
#endif
}
int tmag5170_trigger_set(
const struct device *dev,
const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
struct tmag5170_data *data = dev->data;
#if defined(CONFIG_PM_DEVICE)
enum pm_device_state state;
(void)pm_device_state_get(dev, &state);
if (state != PM_DEVICE_STATE_ACTIVE) {
return -EBUSY;
}
#endif
if (trig->type != SENSOR_TRIG_DATA_READY) {
return -ENOTSUP;
}
data->trigger_drdy = trig;
data->handler_drdy = handler;
return 0;
}
int tmag5170_trigger_init(const struct device *dev)
{
struct tmag5170_data *data = dev->data;
const struct tmag5170_dev_config *cfg = dev->config;
int ret;
if (!device_is_ready(cfg->int_gpio.port)) {
LOG_ERR("%s: device %s is not ready", dev->name, cfg->int_gpio.port->name);
return -ENODEV;
}
data->dev = dev;
#if defined(CONFIG_TMAG5170_TRIGGER_OWN_THREAD)
k_sem_init(&data->sem, 0, 1);
k_thread_create(
&data->thread,
data->thread_stack,
CONFIG_TMAG5170_THREAD_STACK_SIZE,
tmag5170_thread_main,
(void *)dev,
NULL,
NULL,
K_PRIO_COOP(CONFIG_TMAG5170_THREAD_PRIORITY),
0,
K_NO_WAIT);
#elif defined(CONFIG_TMAG5170_TRIGGER_GLOBAL_THREAD)
data->work.handler = tmag5170_work_handler;
#endif
ret = gpio_pin_configure_dt(&cfg->int_gpio, GPIO_INPUT);
if (ret < 0) {
return ret;
}
gpio_init_callback(&data->gpio_cb, tmag5170_gpio_callback, BIT(cfg->int_gpio.pin));
ret = gpio_add_callback(cfg->int_gpio.port, &data->gpio_cb);
if (ret < 0) {
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&cfg->int_gpio, GPIO_INT_EDGE_FALLING);
if (ret < 0) {
return ret;
}
return ret;
}

View file

@ -0,0 +1,213 @@
# Copyright (c) 2023 Michal Morsisko
# SPDX-License-Identifier: Apache-2.0
description: Texas Instruments TMAG5170 high-precision, linear 3D Hall-effect sensor.
compatible: "ti,tmag5170"
include: [sensor-device.yaml, spi-device.yaml]
properties:
int-gpios:
type: phandle-array
description: |
This property specifies the connection to ALERT sensor pin.
It will be used by the driver to notify the application about
data ready event. For this property to take effect, the
TMAG5170_TRIGGER must be set in project configuration
operating-mode:
type: int
required: true
description: |
Operating mode of the device.
1 - stand-by mode - in this mode the device waits for application to trigger
the measurement.
2 - active measure mode - continuous sampling on all enabled channels
as fast as possible. Recommended for devices that haven't got
strict power requirements and need frequent sampling.
3 - active trigger mode - in this mode, similar to stand-by mode, the device
wait for application to trigger the measurement, but the time needed to finish
the conversion is shorter than in stand-by mode, on the cost of increased power
consumption.
4 - duty-cycled - after each sample the device goes to sleep and then
automatically wakes up to take another sample. The sleep time is determined
by `sleep-time` property. Recommended for low-power devices that don't need
high frequency sampling.
enum:
- 1
- 2
- 3
- 4
magnetic-channels:
type: string
default: "XYZ"
description: |
Enables data acquisition of the magnetic axis channel(s)
If axis is enabled more than once, sensor will do pseudo-simultaneous
sampling. Refer to datasheet for more information, By default all axes
are enabled (XYZ) to allow the user to check if the sensor work as expected.
Following options are allowed:
None (chip reset value)
X
Y
XY
Z
ZX
YZ
XYZ (default)
XYX
YXY
YZY
ZYZ
ZXZ
XZX
XYZYX
XYZZYX
enum:
- "None"
- "X"
- "Y"
- "XY"
- "Z"
- "ZX"
- "YZ"
- "XYZ"
- "XYX"
- "YXY"
- "YZY"
- "ZYZ"
- "ZXZ"
- "XZX"
- "XYZYX"
- "XYZZYX"
x-range:
type: int
default: 0
description: |
The maximum and minimum values that can be measured on X axis.
The wider the range, the worse the resolution.
0 = ±50mT (TMAG5170A1)/ ±150mT(TMAG5170A2) - (default; chip reset value)
1 = ±25mT (TMAG5170A1)/ ±75mT(TMAG5170A2)
2 = ±100mT (TMAG5170A1)/ ±300mT(TMAG5170A2)
enum:
- 0
- 1
- 2
y-range:
type: int
default: 0
description: |
The maximum and minimum values that can be measured on Y axis.
The wider the range, the worse the resolution.
0 = ±50mT (TMAG5170A1)/ ±150mT(TMAG5170A2) - (default; chip reset value)
1 = ±25mT (TMAG5170A1)/ ±75mT(TMAG5170A2)
2 = ±100mT (TMAG5170A1)/ ±300mT(TMAG5170A2)
enum:
- 0
- 1
- 2
z-range:
type: int
default: 0
description: |
The maximum and minimum values that can be measured on Z axis.
The wider the range, the worse the resolution.
0 = ±50mT (TMAG5170A1)/ ±150mT(TMAG5170A2) - (default; chip reset value)
1 = ±25mT (TMAG5170A1)/ ±75mT(TMAG5170A2)
2 = ±100mT (TMAG5170A1)/ ±300mT(TMAG5170A2)
enum:
- 0
- 1
- 2
oversampling:
type: int
default: 1
description: |
Enables additional sampling of the sensor data to reduce the noise
effect. If temperature channel is enabled, temperature will be oversampled
too, unless `disable-temperature-oversampling` property is present.
Following options are allowed:
1 (default; chip reset value)
2
4
8
16
32
enum:
- 1
- 2
- 4
- 8
- 16
- 32
enable-temperature-channel:
type: boolean
description: |
Enables temperature measurement
magnet-type:
type: string
default: "None"
description: |
Enables temperature compensation basing on the type of magnet.
Following options are allowed:
None (default; chip reset value)
NdBFe = 0.12%/deg C
SmCo = 0.03%/deg C
Ceramic = 0.2%/deg C
enum:
- "None"
- "NdBFe"
- "SmCo"
- "Ceramic"
angle-measurement:
type: string
default: "None"
description: |
Enable angle calculation using two axis data:
None (default; chip reset value)
XY
YZ
XZ
enum:
- "None"
- "XY"
- "YZ"
- "XZ"
disable-temperature-oversampling:
type: boolean
description: |
If true, temperature is always sampled once per conversion set
If false, temperature is oversampled according to `oversampling`
property.
sleep-time:
type: int
default: 1
description: |
The time in miliseconds the sensor will be in sleep during conversions.
For this property to take effect sensor must be in `duty-cycled` mode.
Note that to calculate total time between conversions, the conversion time
itself must be taken into account. The conversion time is dependent
on the values of `oversampling`, `magnetic-channels`, `temperature-channel-enabled`
and `disable-temperature-oversampling` properties.
Following value are allowed:
1 (default; chip reset value)
5
10
15
20
30
50
100
500
1000
enum:
- 1
- 5
- 10
- 15
- 20
- 30
- 50
- 100
- 500
- 1000

View file

@ -119,7 +119,8 @@
<&test_gpio 0 0>, <&test_gpio 0 0>,
<&test_gpio 0 0>, <&test_gpio 0 0>,
<&test_gpio 0 0>, <&test_gpio 0 0>,
<&test_gpio 0 0>; /* 0x24 */ <&test_gpio 0 0>,
<&test_gpio 0 0>; /* 0x25 */
#include "spi.dtsi" #include "spi.dtsi"
}; };

View file

@ -48,6 +48,7 @@ CONFIG_SM351LT_TRIGGER_GLOBAL_THREAD=y
CONFIG_STTS751_TRIGGER_GLOBAL_THREAD=y CONFIG_STTS751_TRIGGER_GLOBAL_THREAD=y
CONFIG_SX9500_TRIGGER_GLOBAL_THREAD=y CONFIG_SX9500_TRIGGER_GLOBAL_THREAD=y
CONFIG_TCN75A_TRIGGER_GLOBAL_THREAD=y CONFIG_TCN75A_TRIGGER_GLOBAL_THREAD=y
CONFIG_TMAG5170_TRIGGER_GLOBAL_THREAD=y
CONFIG_TMD2620_TRIGGER_GLOBAL_THREAD=y CONFIG_TMD2620_TRIGGER_GLOBAL_THREAD=y
CONFIG_TMP007_TRIGGER_GLOBAL_THREAD=y CONFIG_TMP007_TRIGGER_GLOBAL_THREAD=y
CONFIG_TSL2540_TRIGGER_GLOBAL_THREAD=y CONFIG_TSL2540_TRIGGER_GLOBAL_THREAD=y

View file

@ -48,6 +48,7 @@ CONFIG_SM351LT_TRIGGER_NONE=y
CONFIG_STTS751_TRIGGER_NONE=y CONFIG_STTS751_TRIGGER_NONE=y
CONFIG_SX9500_TRIGGER_NONE=y CONFIG_SX9500_TRIGGER_NONE=y
CONFIG_TCN75A_TRIGGER_NONE=y CONFIG_TCN75A_TRIGGER_NONE=y
CONFIG_TMAG5170_TRIGGER_NONE=y
CONFIG_TMD2620_TRIGGER_NONE=y CONFIG_TMD2620_TRIGGER_NONE=y
CONFIG_TMP007_TRIGGER_NONE=y CONFIG_TMP007_TRIGGER_NONE=y
CONFIG_TSL2540_TRIGGER_NONE=y CONFIG_TSL2540_TRIGGER_NONE=y

View file

@ -46,6 +46,7 @@ CONFIG_SM351LT_TRIGGER_OWN_THREAD=y
CONFIG_STTS751_TRIGGER_OWN_THREAD=y CONFIG_STTS751_TRIGGER_OWN_THREAD=y
CONFIG_SX9500_TRIGGER_OWN_THREAD=y CONFIG_SX9500_TRIGGER_OWN_THREAD=y
CONFIG_TCN75A_TRIGGER_OWN_THREAD=y CONFIG_TCN75A_TRIGGER_OWN_THREAD=y
CONFIG_TMAG5170_TRIGGER_OWN_THREAD=y
CONFIG_TMP007_TRIGGER_OWN_THREAD=y CONFIG_TMP007_TRIGGER_OWN_THREAD=y
CONFIG_TSL2540_TRIGGER_OWN_THREAD=y CONFIG_TSL2540_TRIGGER_OWN_THREAD=y
CONFIG_VCNL4040_TRIGGER_OWN_THREAD=y CONFIG_VCNL4040_TRIGGER_OWN_THREAD=y

View file

@ -287,3 +287,11 @@ test_spi_bmi08x_gyro: bmi08x@24 {
gyro-hz = "1000_116"; gyro-hz = "1000_116";
gyro-fs = <1000>; gyro-fs = <1000>;
}; };
test_spi_tmag5170: tmag5170@25 {
compatible = "ti,tmag5170";
reg = <0x25>;
spi-max-frequency = <0>;
int-gpios = <&test_gpio 0 0>;
operating-mode = <1>;
};