blob: 2e34f52dfabcf6cb5d9397d30537ed4b547f9052 [file] [log] [blame]
/*
* 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)