From 5dddf9f0f8d89470e1cd63d61feb2ce40b7e7070 Mon Sep 17 00:00:00 2001 From: Pete Dietl Date: Mon, 13 Dec 2021 12:49:49 +0100 Subject: [PATCH] drivers: sensors: Implement MAX31875 sensor This commit implements the temperature sensor interface for the Maxim MAX31875Low-Power I2C Temperature Sensor. Signed-off-by: Pete Dietl --- drivers/sensor/CMakeLists.txt | 1 + drivers/sensor/Kconfig | 2 + drivers/sensor/max31875/CMakeLists.txt | 5 + drivers/sensor/max31875/Kconfig | 10 + drivers/sensor/max31875/max31875.c | 275 ++++++++++++++++++++++++ dts/bindings/sensor/maxim,max31875.yaml | 50 +++++ tests/drivers/build_all/sensor/i2c.dtsi | 6 + tests/drivers/build_all/sensor/prj.conf | 1 + 8 files changed, 350 insertions(+) create mode 100644 drivers/sensor/max31875/CMakeLists.txt create mode 100644 drivers/sensor/max31875/Kconfig create mode 100644 drivers/sensor/max31875/max31875.c create mode 100644 dts/bindings/sensor/maxim,max31875.yaml diff --git a/drivers/sensor/CMakeLists.txt b/drivers/sensor/CMakeLists.txt index fb4dde2e7fa..d75cb527f27 100644 --- a/drivers/sensor/CMakeLists.txt +++ b/drivers/sensor/CMakeLists.txt @@ -61,6 +61,7 @@ add_subdirectory_ifdef(CONFIG_LSM9DS0_MFD lsm9ds0_mfd) add_subdirectory_ifdef(CONFIG_MAX17055 max17055) add_subdirectory_ifdef(CONFIG_MAX17262 max17262) add_subdirectory_ifdef(CONFIG_MAX30101 max30101) +add_subdirectory_ifdef(CONFIG_MAX31875 max31875) add_subdirectory_ifdef(CONFIG_MAX44009 max44009) add_subdirectory_ifdef(CONFIG_MAX6675 max6675) add_subdirectory_ifdef(CONFIG_MCP9808 mcp9808) diff --git a/drivers/sensor/Kconfig b/drivers/sensor/Kconfig index 2909d735e86..9f33dab38e1 100644 --- a/drivers/sensor/Kconfig +++ b/drivers/sensor/Kconfig @@ -160,6 +160,8 @@ source "drivers/sensor/max17262/Kconfig" source "drivers/sensor/max30101/Kconfig" +source "drivers/sensor/max31875/Kconfig" + source "drivers/sensor/max44009/Kconfig" source "drivers/sensor/max6675/Kconfig" diff --git a/drivers/sensor/max31875/CMakeLists.txt b/drivers/sensor/max31875/CMakeLists.txt new file mode 100644 index 00000000000..093f1f8f054 --- /dev/null +++ b/drivers/sensor/max31875/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(max31875.c) diff --git a/drivers/sensor/max31875/Kconfig b/drivers/sensor/max31875/Kconfig new file mode 100644 index 00000000000..5d2957aac63 --- /dev/null +++ b/drivers/sensor/max31875/Kconfig @@ -0,0 +1,10 @@ +# MAX31875 temperature sensor configuration options + +# Copyright (c) 2021 Pete Dietl +# SPDX-License-Identifier: Apache-2.0 + +config MAX31875 + bool "MAX31875 Temperature Sensor" + depends on I2C + help + Enable the driver for Maxim MAX31875 Low-Power I2C Temperature Sensors. diff --git a/drivers/sensor/max31875/max31875.c b/drivers/sensor/max31875/max31875.c new file mode 100644 index 00000000000..0cb9a1043e6 --- /dev/null +++ b/drivers/sensor/max31875/max31875.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2021 Pete Dietl + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT maxim_max31875 + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(MAX31875, CONFIG_SENSOR_LOG_LEVEL); + +/* Conversions per second */ +#define MAX31875_CONV_PER_SEC_SHIFT 0x01 +#define MAX31875_CONV_PER_SEC_0_25 0x00 +#define MAX31875_CONV_PER_SEC_1 0x01 +#define MAX31875_CONV_PER_SEC_4 0x02 +#define MAX31875_CONV_PER_SEC_8 0x03 + +#define MAX31875_CONV_PER_SEC_MASK (BIT_MASK(2) << MAX31875_CONV_PER_SEC_SHIFT) + +/* Data format */ +#define MAX31875_DATA_FORMAT_SHIFT 0x07 + +#define MAX31875_DATA_FORMAT_NORMAL 0x00 +#define MAX31875_DATA_FORMAT_EXTENDED 0x01 + +/* These are for shifting the data received */ +#define MAX31875_DATA_FORMAT_EXTENDED_SHIFT 0x03 +#define MAX31875_DATA_FORMAT_NORMAL_SHIFT 0x04 + +/* Resolution in bits */ +#define MAX31875_RESOLUTION_SHIFT 0x05 + +#define MAX31875_RESOLUTION_8_BITS 0x00 +#define MAX31875_RESOLUTION_9_BITS 0x01 +#define MAX31875_RESOLUTION_10_BITS 0x02 +#define MAX31875_RESOLUTION_12_BITS 0x03 + +#define MAX31875_TEMP_SCALE 62500 + +/** + * @brief Macro for creating the MAX31875 configuration register value + * + * @param mode Data format + * @param res Resolution (number of bits) + * @param convs Conversions per second + */ +#define MAX31875_CONFIG(format, res, convs) \ + (((format) << (MAX31875_DATA_FORMAT_SHIFT)) | ((res) << (MAX31875_RESOLUTION_SHIFT)) | \ + ((convs) << (MAX31875_CONV_PER_SEC_SHIFT))) + +#define MAX31875_REG_TEMPERATURE 0x00 +#define MAX31875_REG_CONFIG 0x01 + +struct max31875_data { + int16_t sample; + uint16_t config_reg; +}; + +struct max31875_config { + const struct i2c_dt_spec bus; + uint8_t conversions_per_second; + uint8_t data_format; + uint8_t resolution; +}; + +static int max31875_reg_read(const struct max31875_config *cfg, + uint8_t reg, uint16_t *val) +{ + int ret; + + ret = i2c_burst_read_dt(&cfg->bus, reg, (uint8_t *)val, sizeof(*val)); + if (ret < 0) { + return ret; + } + + *val = sys_be16_to_cpu(*val); + return 0; +} + +static int max31875_reg_write(const struct max31875_config *cfg, + uint8_t reg, uint16_t val) +{ + uint16_t val_be = sys_cpu_to_be16(val); + + return i2c_burst_write_dt(&cfg->bus, reg, (uint8_t *)&val_be, 2); +} + +static uint16_t set_config_flags(struct max31875_data *data, uint16_t mask, + uint16_t value) +{ + return (data->config_reg & ~mask) | (value & mask); +} + +static int max31875_update_config(const struct device *dev, uint16_t mask, uint16_t val) +{ + int rc; + const struct max31875_config *cfg = dev->config; + struct max31875_data *data = dev->data; + const uint16_t new_val = set_config_flags(data, mask, val); + + rc = max31875_reg_write(cfg, MAX31875_REG_CONFIG, new_val); + + /* don't update if write failed */ + if (rc == 0) { + data->config_reg = new_val; + } + + return rc; +} + +static int max31875_attr_set(const struct device *dev, + enum sensor_channel chan, + enum sensor_attribute attr, + const struct sensor_value *val) +{ + int ret; + uint16_t value; + uint16_t cr; + + if (chan != SENSOR_CHAN_AMBIENT_TEMP) { + return -ENOTSUP; + } + + switch (attr) { + case SENSOR_ATTR_FULL_SCALE: + /* the sensor supports two ranges -50 to 128 and -50 to 150 */ + /* the value contains the upper limit */ + if (val->val1 == 128) { + value = MAX31875_DATA_FORMAT_NORMAL << MAX31875_DATA_FORMAT_SHIFT; + } else if (val->val1 == 150) { + value = MAX31875_DATA_FORMAT_EXTENDED << MAX31875_DATA_FORMAT_SHIFT; + } else { + return -ENOTSUP; + } + + ret = max31875_update_config(dev, MAX31875_DATA_FORMAT_SHIFT, value); + if (ret < 0) { + LOG_ERR("Failed to set attribute!"); + return ret; + } + break; + case SENSOR_ATTR_SAMPLING_FREQUENCY: + /* conversion rate in mHz */ + cr = val->val1 * 1000 + val->val2 / 1000; + + /* the sensor supports 0.25Hz, 1Hz, 4Hz and 8Hz */ + /* conversion rate */ + switch (cr) { + case 250: + value = MAX31875_CONV_PER_SEC_0_25 << MAX31875_CONV_PER_SEC_SHIFT; + break; + + case 1000: + value = MAX31875_CONV_PER_SEC_1 << MAX31875_CONV_PER_SEC_SHIFT; + break; + + case 4000: + value = MAX31875_CONV_PER_SEC_4 << MAX31875_CONV_PER_SEC_SHIFT; + break; + + case 8000: + value = MAX31875_CONV_PER_SEC_8 << MAX31875_CONV_PER_SEC_SHIFT; + break; + + default: + return -ENOTSUP; + } + + ret = max31875_update_config(dev, MAX31875_CONV_PER_SEC_MASK, value); + if (ret < 0) { + LOG_ERR("Failed to set attribute!"); + return ret; + } + + break; + + default: + return -ENOTSUP; + } + + return 0; +} + +static int max31875_sample_fetch(const struct device *dev, + enum sensor_channel chan) +{ + struct max31875_data *data = dev->data; + const struct max31875_config *cfg = dev->config; + uint16_t val; + int ret; + + if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP) { + LOG_ERR("Invalid channel provided"); + return -ENOTSUP; + } + + ret = max31875_reg_read(cfg, MAX31875_REG_TEMPERATURE, &val); + if (ret < 0) { + return ret; + } + + if (data->config_reg & BIT(MAX31875_DATA_FORMAT_SHIFT)) { + data->sample = arithmetic_shift_right((int16_t)val, + MAX31875_DATA_FORMAT_EXTENDED_SHIFT); + } else { + data->sample = arithmetic_shift_right((int16_t)val, + MAX31875_DATA_FORMAT_NORMAL_SHIFT); + } + + return 0; +} + +static int max31875_channel_get(const struct device *dev, + enum sensor_channel chan, + struct sensor_value *val) +{ + struct max31875_data *data = dev->data; + int32_t uval; + + if (chan != SENSOR_CHAN_AMBIENT_TEMP) { + return -ENOTSUP; + } + + uval = data->sample * MAX31875_TEMP_SCALE; + val->val1 = uval / 1000000; + val->val2 = uval % 1000000; + + return 0; +} + +static const struct sensor_driver_api max31875_driver_api = { + .attr_set = max31875_attr_set, + .sample_fetch = max31875_sample_fetch, + .channel_get = max31875_channel_get, +}; + +static int max31875_init(const struct device *dev) +{ + const struct max31875_config *cfg = dev->config; + struct max31875_data *data = dev->data; + + if (!device_is_ready(cfg->bus.bus)) { + LOG_ERR("I2C dev %s not ready", cfg->bus.bus->name); + return -ENODEV; + } + + data->config_reg = MAX31875_CONFIG(cfg->data_format, cfg->resolution, + cfg->conversions_per_second); + + return max31875_update_config(dev, 0, 0); +} + + +#define MAX31875_INST(inst) \ + static struct max31875_data max31875_data_##inst; \ + static const struct max31875_config max31875_config_##inst = { \ + .bus = I2C_DT_SPEC_INST_GET(inst), \ + .conversions_per_second = DT_INST_ENUM_IDX(inst, conversions_per_second), \ + .resolution = DT_INST_ENUM_IDX(inst, resolution), \ + .data_format = DT_INST_PROP(inst, extended_mode), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, max31875_init, NULL, &max31875_data_##inst, \ + &max31875_config_##inst, POST_KERNEL, \ + CONFIG_SENSOR_INIT_PRIORITY, &max31875_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(MAX31875_INST) diff --git a/dts/bindings/sensor/maxim,max31875.yaml b/dts/bindings/sensor/maxim,max31875.yaml new file mode 100644 index 00000000000..5fb5dbc5137 --- /dev/null +++ b/dts/bindings/sensor/maxim,max31875.yaml @@ -0,0 +1,50 @@ +# +# Copyright 2022 Pete Dietl +# +# SPDX-License-Identifier: Apache-2.0 +# + +description: | + Maxim MAX31875 Low-Power I2C Temperature Sensor. + Find the datasheet here: + https://datasheets.maximintegrated.com/en/ds/MAX31875.pdf + +compatible: "maxim,max31875" + +include: "i2c-device.yaml" + +properties: + conversions-per-second: + description: | + Number of temperature readings performed by the MAX31875 per second. + 0.25 converions per second is the power-on reset configuration. + type: string + default: "0.25" + # Note: the driver relies on the ordering of this enum, + # so change it with care. + enum: + - "0.25" + - "1" + - "4" + - "8" + extended-mode: + description: | + When true, use 13-bit data format to allow for measuring + temperatures > 128°C + type: boolean + resolution: + description: | + The number of bits used for each temperature sample. + A resolution of 10 bits takes 35ms to convert. + Each 1-bit increase doubles the conversion time. + Similarly, each 1-bit decrease halves the conversion time. + 10 bits of resolution is the power-on reset configuration. + type: int + default: 10 + # Note: the driver relies on the ordering of this enum, + # so change it with care. + enum: # Conversion time (ms) + - 8 # 8.75 + - 9 # 17.5 + - 10 # 35 + - 12 # 140 diff --git a/tests/drivers/build_all/sensor/i2c.dtsi b/tests/drivers/build_all/sensor/i2c.dtsi index 1b2fe44f0a8..d78ec27eb9e 100644 --- a/tests/drivers/build_all/sensor/i2c.dtsi +++ b/tests/drivers/build_all/sensor/i2c.dtsi @@ -725,3 +725,9 @@ test_i2c_ina237: ina237@52 { adc-config = <0>; rshunt = <0>; }; + +test_i2c_max31875: max31875@53 { + compatible = "maxim,max31875"; + label = "MAX31875"; + reg = <0x53>; +}; diff --git a/tests/drivers/build_all/sensor/prj.conf b/tests/drivers/build_all/sensor/prj.conf index f2ac144eb7e..120db1876a6 100644 --- a/tests/drivers/build_all/sensor/prj.conf +++ b/tests/drivers/build_all/sensor/prj.conf @@ -104,3 +104,4 @@ CONFIG_INA23X=y CONFIG_INA230=y CONFIG_INA237=y CONFIG_MHZ19B=y +CONFIG_MAX31875=y