diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index 2aba319c16f..782115ca130 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -135,6 +135,7 @@ add_subdirectory_ifdef(CONFIG_TEMP_NRF5 nrf5) add_subdirectory_ifdef(CONFIG_TH02 th02) add_subdirectory_ifdef(CONFIG_TI_HDC ti_hdc) add_subdirectory_ifdef(CONFIG_TI_HDC20XX ti_hdc20xx) +add_subdirectory_ifdef(CONFIG_TMAG5170 tmag5170) add_subdirectory_ifdef(CONFIG_TMD2620 tmd2620) add_subdirectory_ifdef(CONFIG_TMP007 tmp007) add_subdirectory_ifdef(CONFIG_TMP108 tmp108) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index dd4917cf64d..f406b6d1ffe 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -191,6 +191,7 @@ source "drivers/sensor/tcs3400/Kconfig" source "drivers/sensor/th02/Kconfig" source "drivers/sensor/ti_hdc/Kconfig" source "drivers/sensor/ti_hdc20xx/Kconfig" +source "drivers/sensor/tmag5170/Kconfig" source "drivers/sensor/tmd2620/Kconfig" source "drivers/sensor/tmp007/Kconfig" source "drivers/sensor/tmp108/Kconfig" diff --git a/drivers/sensor/tmag5170/CMakeLists.txt b/drivers/sensor/tmag5170/CMakeLists.txt new file mode 100644 index 00000000000..f196c2019a2 --- /dev/null +++ b/drivers/sensor/tmag5170/CMakeLists.txt @@ -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) diff --git a/drivers/sensor/tmag5170/Kconfig b/drivers/sensor/tmag5170/Kconfig new file mode 100644 index 00000000000..592b2382448 --- /dev/null +++ b/drivers/sensor/tmag5170/Kconfig @@ -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 diff --git a/drivers/sensor/tmag5170/tmag5170.c b/drivers/sensor/tmag5170/tmag5170.c new file mode 100644 index 00000000000..2e34f52dfab --- /dev/null +++ b/drivers/sensor/tmag5170/tmag5170.c @@ -0,0 +1,586 @@ +/* + * Copyright (c) 2023 Michal Morsisko + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_tmag5170 + +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_TMAG5170_CRC) +#include +#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) diff --git a/drivers/sensor/tmag5170/tmag5170.h b/drivers/sensor/tmag5170/tmag5170.h new file mode 100644 index 00000000000..91778e81c56 --- /dev/null +++ b/drivers/sensor/tmag5170/tmag5170.h @@ -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 +#include +#include +#include + +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_ */ diff --git a/drivers/sensor/tmag5170/tmag5170_trigger.c b/drivers/sensor/tmag5170/tmag5170_trigger.c new file mode 100644 index 00000000000..c1be9cd4e1f --- /dev/null +++ b/drivers/sensor/tmag5170/tmag5170_trigger.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2023 Michal Morsisko + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT ti_tmag5170 + +#include +#include +#include +#include +#include + +#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; +} diff --git a/dts/bindings/sensor/ti,tmag5170.yaml b/dts/bindings/sensor/ti,tmag5170.yaml new file mode 100644 index 00000000000..c68e704c900 --- /dev/null +++ b/dts/bindings/sensor/ti,tmag5170.yaml @@ -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 diff --git a/tests/drivers/build_all/sensor/app.overlay b/tests/drivers/build_all/sensor/app.overlay index 629334bc396..a589b62cfa1 100644 --- a/tests/drivers/build_all/sensor/app.overlay +++ b/tests/drivers/build_all/sensor/app.overlay @@ -119,7 +119,8 @@ <&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" }; diff --git a/tests/drivers/build_all/sensor/sensors_trigger_global.conf b/tests/drivers/build_all/sensor/sensors_trigger_global.conf index cf4fce093e4..f0c34845ce0 100644 --- a/tests/drivers/build_all/sensor/sensors_trigger_global.conf +++ b/tests/drivers/build_all/sensor/sensors_trigger_global.conf @@ -48,6 +48,7 @@ CONFIG_SM351LT_TRIGGER_GLOBAL_THREAD=y CONFIG_STTS751_TRIGGER_GLOBAL_THREAD=y CONFIG_SX9500_TRIGGER_GLOBAL_THREAD=y CONFIG_TCN75A_TRIGGER_GLOBAL_THREAD=y +CONFIG_TMAG5170_TRIGGER_GLOBAL_THREAD=y CONFIG_TMD2620_TRIGGER_GLOBAL_THREAD=y CONFIG_TMP007_TRIGGER_GLOBAL_THREAD=y CONFIG_TSL2540_TRIGGER_GLOBAL_THREAD=y diff --git a/tests/drivers/build_all/sensor/sensors_trigger_none.conf b/tests/drivers/build_all/sensor/sensors_trigger_none.conf index f89a07de1c4..2c4b51c2184 100644 --- a/tests/drivers/build_all/sensor/sensors_trigger_none.conf +++ b/tests/drivers/build_all/sensor/sensors_trigger_none.conf @@ -48,6 +48,7 @@ CONFIG_SM351LT_TRIGGER_NONE=y CONFIG_STTS751_TRIGGER_NONE=y CONFIG_SX9500_TRIGGER_NONE=y CONFIG_TCN75A_TRIGGER_NONE=y +CONFIG_TMAG5170_TRIGGER_NONE=y CONFIG_TMD2620_TRIGGER_NONE=y CONFIG_TMP007_TRIGGER_NONE=y CONFIG_TSL2540_TRIGGER_NONE=y diff --git a/tests/drivers/build_all/sensor/sensors_trigger_own.conf b/tests/drivers/build_all/sensor/sensors_trigger_own.conf index 3447061c16f..17e1b6c29fa 100644 --- a/tests/drivers/build_all/sensor/sensors_trigger_own.conf +++ b/tests/drivers/build_all/sensor/sensors_trigger_own.conf @@ -46,6 +46,7 @@ CONFIG_SM351LT_TRIGGER_OWN_THREAD=y CONFIG_STTS751_TRIGGER_OWN_THREAD=y CONFIG_SX9500_TRIGGER_OWN_THREAD=y CONFIG_TCN75A_TRIGGER_OWN_THREAD=y +CONFIG_TMAG5170_TRIGGER_OWN_THREAD=y CONFIG_TMP007_TRIGGER_OWN_THREAD=y CONFIG_TSL2540_TRIGGER_OWN_THREAD=y CONFIG_VCNL4040_TRIGGER_OWN_THREAD=y diff --git a/tests/drivers/build_all/sensor/spi.dtsi b/tests/drivers/build_all/sensor/spi.dtsi index d6764f14d9b..513ffbf4e37 100644 --- a/tests/drivers/build_all/sensor/spi.dtsi +++ b/tests/drivers/build_all/sensor/spi.dtsi @@ -287,3 +287,11 @@ test_spi_bmi08x_gyro: bmi08x@24 { gyro-hz = "1000_116"; 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>; +};