drivers: sensor: introduce driver for TCN75A temperature sensor

Add driver for TCN75A temperature sensor. The following features are
supported:
- TCN75A oneshot mode, which allows single shot conversions with lower
  power consumtion
- Resolution selection, up to 12 bit resolution (9 bit default)
- Triggering based on temperatue thresholds. If the TCN75A exits a set
  threshold range, the application can be notified via a callback.

Signed-off-by: Daniel DeGrasse <daniel@degrasse.com>
This commit is contained in:
Daniel DeGrasse 2022-12-04 00:46:56 -06:00 committed by Anas Nashif
commit 6c10da7957
8 changed files with 521 additions and 0 deletions

View file

@ -120,6 +120,7 @@ add_subdirectory_ifdef(CONFIG_SX9500 sx9500)
add_subdirectory_ifdef(CONFIG_TACH_IT8XXX2 ite_tach_it8xxx2)
add_subdirectory_ifdef(CONFIG_TACH_NPCX nuvoton_tach_npcx)
add_subdirectory_ifdef(CONFIG_TACH_XEC mchp_tach_xec)
add_subdirectory_ifdef(CONFIG_TCN75A tcn75a)
add_subdirectory_ifdef(CONFIG_TCS3400 tcs3400)
add_subdirectory_ifdef(CONFIG_TEMP_KINETIS nxp_kinetis_temp)
add_subdirectory_ifdef(CONFIG_TEMP_NRF5 nrf5)

View file

@ -178,6 +178,7 @@ source "drivers/sensor/stm32_vbat/Kconfig"
source "drivers/sensor/stm32_vref/Kconfig"
source "drivers/sensor/stts751/Kconfig"
source "drivers/sensor/sx9500/Kconfig"
source "drivers/sensor/tcn75a/Kconfig"
source "drivers/sensor/tcs3400/Kconfig"
source "drivers/sensor/th02/Kconfig"
source "drivers/sensor/ti_hdc/Kconfig"

View file

@ -0,0 +1,6 @@
# Copyright 2023 Daniel DeGrasse <daniel@degrasse.com>
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources(tcn75a.c)
zephyr_library_sources_ifdef(CONFIG_TCN75A_TRIGGER tcn75a_trigger.c)

View file

@ -0,0 +1,50 @@
# Copyright 2023 Daniel DeGrasse <daniel@degrasse.com>
# SPDX-License-Identifier: Apache-2.0
config TCN75A
bool "TCN75A Ambient Temperature Sensor"
default y
depends on DT_HAS_MICROCHIP_TCN75A_ENABLED
select I2C
help
Enable TCN75A ambient temperature to digital converter
if TCN75A
choice
prompt "TCN75A Trigger mode"
default TCN75A_TRIGGER_NONE
config TCN75A_TRIGGER_NONE
bool "No trigger"
config TCN75A_TRIGGER_OWN_THREAD
bool "Use own thread"
select TCN75A_TRIGGER
config TCN75A_TRIGGER_GLOBAL_THREAD
bool "Use global thread"
select TCN75A_TRIGGER
endchoice
config TCN75A_TRIGGER
bool
help
Enable interrupt based trigger support for TCN75A. Requires that
the sensor be set to continuous sample mode.
if TCN75A_TRIGGER_OWN_THREAD
config TCN75A_THREAD_PRIORITY
int "Own thread priority"
default 10
config TCN75A_THREAD_STACK_SIZE
int "Own thread stack size"
default 1024
endif # TCN75A_TRIGGER_OWN_THREAD
endif # TCN75A

View file

@ -0,0 +1,135 @@
/*
* Copyright 2023 Daniel DeGrasse <daniel@degrasse.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT microchip_tcn75a
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(tcn75a, CONFIG_SENSOR_LOG_LEVEL);
#include "tcn75a.h"
int tcn75a_sample_fetch(const struct device *dev, enum sensor_channel chan)
{
const struct tcn75a_config *config = dev->config;
struct tcn75a_data *data = dev->data;
int ret;
uint8_t temp_reg = TCN75A_TEMP_REG;
uint8_t rx_buf[2];
uint8_t adc_conf[2] = {TCN75A_CONFIG_REG, 0x0};
/* This sensor only supports ambient temperature */
if ((chan != SENSOR_CHAN_ALL) && (chan != SENSOR_CHAN_AMBIENT_TEMP)) {
return -ENOTSUP;
}
if (config->oneshot_mode) {
/* Oneshot mode, requires one shot bit to be set in config register */
adc_conf[1] = TCN75A_CONFIG_ONEDOWN;
ret = i2c_write_dt(&config->i2c_spec, adc_conf, 2);
if (ret < 0) {
return ret;
}
}
/* Fetch a sample from the 2 byte ambient temperature register */
ret = i2c_write_read_dt(&config->i2c_spec, &temp_reg, sizeof(temp_reg),
rx_buf, sizeof(rx_buf));
if (ret < 0) {
return ret;
}
data->temp_sample = sys_get_be16(rx_buf);
LOG_DBG("Raw sample: 0x%04x", data->temp_sample);
return ret;
}
static int tcn75a_channel_get(const struct device *dev, enum sensor_channel chan,
struct sensor_value *val)
{
struct tcn75a_data *data = dev->data;
uint32_t temp_lsb;
if (chan != SENSOR_CHAN_AMBIENT_TEMP) {
return -ENOTSUP;
}
/* Convert fixed point to sensor value */
val->val1 = data->temp_sample >> TCN75A_TEMP_MSB_POS;
temp_lsb = (data->temp_sample & TCN75A_TEMP_LSB_MASK);
val->val2 = TCN75A_FIXED_PT_TO_SENSOR(temp_lsb);
return 0;
}
static const struct sensor_driver_api tcn75a_api = {
.sample_fetch = &tcn75a_sample_fetch,
.channel_get = &tcn75a_channel_get,
#ifdef CONFIG_TCN75A_TRIGGER
.attr_get = &tcn75a_attr_get,
.attr_set = &tcn75a_attr_set,
.trigger_set = &tcn75a_trigger_set,
#endif
};
static int tcn75a_init(const struct device *dev)
{
const struct tcn75a_config *config = dev->config;
uint8_t adc_conf[2] = {TCN75A_CONFIG_REG, 0x0};
if (!i2c_is_ready_dt(&config->i2c_spec)) {
LOG_ERR("I2C bus is not ready");
return -ENODEV;
}
/* Set user selected resolution */
adc_conf[1] |= TCN75A_CONFIG_RES(config->resolution);
if (config->oneshot_mode) {
if (adc_conf[1] != 0) {
/* Oneshot mode only supports 9 bit resolution */
LOG_ERR("Oneshot mode requires 9 bit resolution");
return -ENODEV;
}
adc_conf[1] |= TCN75A_CONFIG_SHUTDOWN;
}
#ifdef CONFIG_TCN75A_TRIGGER
/* If user supplies an ALERT gpio, assume they want trigger support. */
if (config->alert_gpios.port != NULL) {
int ret;
if (config->oneshot_mode) {
LOG_ERR("Oneshot mode not supported with trigger");
return -ENODEV;
}
ret = tcn75a_trigger_init(dev);
if (ret < 0) {
return ret;
}
}
#endif
return i2c_write_dt(&config->i2c_spec, adc_conf, 2);
}
#ifdef CONFIG_TCN75A_TRIGGER
#define TCN75A_TRIGGER(n) .alert_gpios = GPIO_DT_SPEC_INST_GET_OR(n, alert_gpios, {}),
#else
#define TCN75A_TRIGGER(n)
#endif
#define TCN75A_INIT(n) \
static struct tcn75a_data tcn75a_data_##n; \
static const struct tcn75a_config tcn75a_config_##n = { \
.i2c_spec = I2C_DT_SPEC_INST_GET(n), \
.resolution = DT_INST_ENUM_IDX(n, resolution), \
.oneshot_mode = DT_INST_PROP(n, oneshot_mode), \
TCN75A_TRIGGER(n) \
}; \
SENSOR_DEVICE_DT_INST_DEFINE(n, &tcn75a_init, NULL, &tcn75a_data_##n, &tcn75a_config_##n, \
POST_KERNEL, CONFIG_SENSOR_INIT_PRIORITY, &tcn75a_api);
DT_INST_FOREACH_STATUS_OKAY(TCN75A_INIT)

View file

@ -0,0 +1,83 @@
/*
* Copyright 2023 Daniel DeGrasse <daniel@degrasse.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef _DRIVERS_SENSOR_TCN75A_H_
#define _DRIVERS_SENSOR_TCN75A_H_
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/i2c.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/sys/byteorder.h>
#define TCN75A_TEMP_REG 0x0
#define TCN75A_CONFIG_REG 0x1
#define TCN75A_THYST_REG 0x2
#define TCN75A_TSET_REG 0x3
/* TCN75A TEMP register constants */
#define TCN75A_TEMP_MSB_POS 8
#define TCN75A_TEMP_MSB_MASK 0xFF00
#define TCN75A_TEMP_LSB_MASK 0xFF
#define TCN75A_TEMP_LSB_POS 0
/* TCN75A CONFIG register constants */
#define TCN75A_CONFIG_ONEDOWN BIT(7)
#define TCN75A_CONFIG_RES(x) (((x) & 0x3) << 5)
#define TCN75A_CONFIG_INT_EN 0x2
#define TCN75A_CONFIG_SHUTDOWN 0x1
struct tcn75a_config {
struct i2c_dt_spec i2c_spec;
bool oneshot_mode;
uint8_t resolution;
#ifdef CONFIG_TCN75A_TRIGGER
struct gpio_dt_spec alert_gpios;
#endif
};
struct tcn75a_data {
uint16_t temp_sample;
#ifdef CONFIG_TCN75A_TRIGGER
const struct device *dev;
struct gpio_callback gpio_cb;
sensor_trigger_handler_t sensor_cb;
const struct sensor_trigger *sensor_trig;
#endif
#ifdef CONFIG_TCN75A_TRIGGER_GLOBAL_THREAD
struct k_work work;
#endif
#ifdef CONFIG_TCN75A_TRIGGER_OWN_THREAD
K_KERNEL_STACK_MEMBER(thread_stack, CONFIG_TCN75A_THREAD_STACK_SIZE);
struct k_thread thread;
struct k_sem trig_sem;
#endif
};
/* Helpers to convert from TCN75A temperature fixed point format
* to sensor val2 format. When the LSB of the TCN75A temperature sample
* is treated as an integer, the format to convert to sensor val2 is
* FIXED_POINT_VAL * 3906.25
*/
#define TCN75A_FIXED_PT_TO_SENSOR(x) (((x)*3906) + ((x) >> 2))
/* This conversion is imprecise, but because the 4 least significant bits
* of the temperature register aren't used, it doesn't matter.
*/
#define TCN75A_SENSOR_TO_FIXED_PT(x) ((x) / 3906)
#ifdef CONFIG_TCN75A_TRIGGER
int tcn75a_trigger_init(const struct device *dev);
int tcn75a_attr_get(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr,
struct sensor_value *val);
int tcn75a_attr_set(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr,
const struct sensor_value *val);
int tcn75a_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler);
#endif
int tcn75a_sample_fetch(const struct device *dev, enum sensor_channel chan);
#endif /* _DRIVERS_SENSOR_TCN75A_H_ */

View file

@ -0,0 +1,203 @@
/*
* Copyright 2023 Daniel DeGrasse <daniel@degrasse.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(tcn75a, CONFIG_SENSOR_LOG_LEVEL);
#include "tcn75a.h"
int tcn75a_trigger_set(const struct device *dev, const struct sensor_trigger *trig,
sensor_trigger_handler_t handler)
{
const struct tcn75a_config *config = dev->config;
struct tcn75a_data *data = dev->data;
int ret;
if (trig->type != SENSOR_TRIG_THRESHOLD) {
return -ENOTSUP;
}
if ((trig->chan != SENSOR_CHAN_ALL) && (trig->chan != SENSOR_CHAN_AMBIENT_TEMP)) {
return -ENOTSUP;
}
data->sensor_cb = handler;
data->sensor_trig = trig;
/* TCN75A starts in comparator mode by default, switch it to
* use interrupt mode.
*/
ret = i2c_reg_update_byte_dt(&config->i2c_spec, TCN75A_CONFIG_REG, TCN75A_CONFIG_INT_EN,
TCN75A_CONFIG_INT_EN);
if (ret < 0) {
return ret;
}
ret = gpio_pin_interrupt_configure_dt(&config->alert_gpios, GPIO_INT_EDGE_TO_ACTIVE);
if (ret < 0) {
return ret;
}
return ret;
}
int tcn75a_attr_set(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr,
const struct sensor_value *val)
{
const struct tcn75a_config *config = dev->config;
uint8_t tx_buf[3];
if ((chan != SENSOR_CHAN_AMBIENT_TEMP) && (chan != SENSOR_CHAN_ALL)) {
return -ENOTSUP;
}
switch (attr) {
case SENSOR_ATTR_LOWER_THRESH:
tx_buf[0] = TCN75A_THYST_REG;
break;
case SENSOR_ATTR_UPPER_THRESH:
tx_buf[0] = TCN75A_TSET_REG;
break;
default:
return -ENOTSUP;
}
/* Convert sensor val to fixed point */
tx_buf[1] = (uint8_t)val->val1;
tx_buf[2] = TCN75A_SENSOR_TO_FIXED_PT(val->val2);
LOG_DBG("Writing 0x%02X to limit reg %s", *(uint16_t *)(tx_buf + 1),
tx_buf[0] == TCN75A_THYST_REG ? "THYST" : "TSET");
return i2c_write_dt(&config->i2c_spec, tx_buf, 3);
}
int tcn75a_attr_get(const struct device *dev, enum sensor_channel chan, enum sensor_attribute attr,
struct sensor_value *val)
{
const struct tcn75a_config *config = dev->config;
uint8_t config_reg;
uint8_t rx_buf[2];
uint16_t limit, temp_lsb;
int ret;
if ((chan != SENSOR_CHAN_AMBIENT_TEMP) && (chan != SENSOR_CHAN_ALL)) {
return -ENOTSUP;
}
switch (attr) {
case SENSOR_ATTR_LOWER_THRESH:
config_reg = TCN75A_THYST_REG;
break;
case SENSOR_ATTR_UPPER_THRESH:
config_reg = TCN75A_TSET_REG;
break;
default:
return -ENOTSUP;
}
ret = i2c_write_read_dt(&config->i2c_spec, &config_reg, 1, rx_buf, 2);
if (ret < 0) {
return ret;
}
limit = sys_get_be16(rx_buf);
LOG_DBG("Read 0x%02X from %s", limit, config_reg == TCN75A_THYST_REG ? "THYST" : "TSET");
/* Convert fixed point to sensor value */
val->val1 = limit >> TCN75A_TEMP_MSB_POS;
temp_lsb = (limit & TCN75A_TEMP_LSB_MASK);
val->val2 = TCN75A_FIXED_PT_TO_SENSOR(temp_lsb);
return ret;
}
static void tcn75a_handle_int(const struct device *dev)
{
struct tcn75a_data *data = dev->data;
/* Note that once the temperature rises
* above T_SET, the sensor will not trigger another interrupt until
* it falls below T_HYST (or vice versa for falling below T_HYST).
*
* Reading from any register will de-assert the interrupt.
*/
if (data->sensor_cb) {
data->sensor_cb(dev, data->sensor_trig);
}
}
static void tcn75a_gpio_callback(const struct device *dev, struct gpio_callback *cb,
uint32_t pin_mask)
{
struct tcn75a_data *data = CONTAINER_OF(cb, struct tcn75a_data, gpio_cb);
const struct tcn75a_config *config = data->dev->config;
if ((pin_mask & BIT(config->alert_gpios.pin)) == 0U) {
return;
}
#if defined(CONFIG_TCN75A_TRIGGER_OWN_THREAD)
k_sem_give(&data->trig_sem);
#elif defined(CONFIG_TCN75A_TRIGGER_GLOBAL_THREAD)
k_work_submit(&data->work);
#endif
}
#ifdef CONFIG_TCN75A_TRIGGER_OWN_THREAD
static void tcn75a_thread_main(struct tcn75a_data *data)
{
while (true) {
k_sem_take(&data->trig_sem, K_FOREVER);
tcn75a_handle_int(data->dev);
}
}
#endif
#ifdef CONFIG_TCN75A_TRIGGER_GLOBAL_THREAD
static void tcn75a_work_handler(struct k_work *work)
{
struct tcn75a_data *data = CONTAINER_OF(work, struct tcn75a_data, work);
tcn75a_handle_int(data->dev);
}
#endif
int tcn75a_trigger_init(const struct device *dev)
{
const struct tcn75a_config *config = dev->config;
struct tcn75a_data *data = dev->data;
int ret;
/* Save config pointer */
data->dev = dev;
if (!gpio_is_ready_dt(&config->alert_gpios)) {
LOG_ERR("alert GPIO device is not ready");
return -ENODEV;
}
ret = gpio_pin_configure_dt(&config->alert_gpios, GPIO_INPUT);
if (ret < 0) {
return ret;
}
gpio_init_callback(&data->gpio_cb, tcn75a_gpio_callback, BIT(config->alert_gpios.pin));
ret = gpio_add_callback(config->alert_gpios.port, &data->gpio_cb);
#if defined(CONFIG_TCN75A_TRIGGER_OWN_THREAD)
k_sem_init(&data->trig_sem, 0, K_SEM_MAX_LIMIT);
k_thread_create(&data->thread, data->thread_stack, CONFIG_TCN75A_THREAD_STACK_SIZE,
(k_thread_entry_t)tcn75a_thread_main, data, NULL, NULL,
K_PRIO_COOP(CONFIG_TCN75A_THREAD_PRIORITY), 0, K_NO_WAIT);
#elif defined(CONFIG_TCN75A_TRIGGER_GLOBAL_THREAD)
data->work.handler = tcn75a_work_handler;
#endif
return ret;
}

View file

@ -0,0 +1,42 @@
# Copyright 2023 Daniel DeGrasse <daniel@degrasse.com>
# SPDX-License-Identifier: Apache-2.0
description: TCN75A ambient temperature sensor
compatible: "microchip,tcn75a"
include: [sensor-device.yaml, i2c-device.yaml]
properties:
alert-gpios:
type: phandle-array
description: |
ALERT pin
This pin defaults to active low when produced by the sensor.
The property value should ensure the gpio flags properly describe
the signal that is presented to the driver. Required in order to use
triggering support.
resolution:
type: string
default: "9-bit"
enum:
- "9-bit"
- "10-bit"
- "11-bit"
- "12-bit"
description: |
Sensor resolution. Higher resolutions will result in longer conversion
times. Note: datasheet's claim about the ambient temperature register:
> When the 0.5°C, 0.25°C or 0.125°C resolutions are selected,
> bit 6, bit 7 or bit 8 will remain clear <0>,
> respectively.
appears to be incorrect. Only conversion times seem to be affected by
resolution selection.
oneshot-mode:
type: boolean
description: |
Oneshot sampling mode. Reduces power consumption, but disables triggering
feature as well as high resolution sampling. Only supported with 9 bit
resolution.