| /* |
| * 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(void *p1, void *p2, void *p3) |
| { |
| ARG_UNUSED(p2); |
| ARG_UNUSED(p3); |
| |
| struct tcn75a_data *data = p1; |
| |
| 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, |
| 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; |
| } |