| /* |
| * Copyright (c) 2021 Jimmy Johnson <catch22@fastmail.net> |
| * Copyright (c) 2022 T-Mobile USA, Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ti_tmp108 |
| |
| #include <zephyr/device.h> |
| #include <zephyr/drivers/i2c.h> |
| #include <zephyr/drivers/sensor.h> |
| #include <zephyr/sys/util.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/logging/log.h> |
| #include <zephyr/kernel.h> |
| |
| #include "tmp108.h" |
| |
| LOG_MODULE_REGISTER(TMP108, CONFIG_SENSOR_LOG_LEVEL); |
| |
| int tmp108_reg_read(const struct device *dev, uint8_t reg, uint16_t *val) |
| { |
| const struct tmp108_config *cfg = dev->config; |
| int result; |
| |
| result = i2c_burst_read_dt(&cfg->i2c_spec, reg, (uint8_t *) val, 2); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| *val = sys_be16_to_cpu(*val); |
| |
| return 0; |
| } |
| |
| int tmp108_reg_write(const struct device *dev, uint8_t reg, uint16_t val) |
| { |
| const struct tmp108_config *cfg = dev->config; |
| uint8_t tx_buf[3]; |
| int result; |
| |
| tx_buf[0] = reg; |
| sys_put_be16(val, &tx_buf[1]); |
| |
| result = i2c_write_dt(&cfg->i2c_spec, tx_buf, sizeof(tx_buf)); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| int tmp108_write_config(const struct device *dev, uint16_t mask, uint16_t conf) |
| { |
| uint16_t config = 0; |
| int result; |
| |
| result = tmp108_reg_read(dev, TI_TMP108_REG_CONF, &config); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| config &= mask; |
| config |= conf; |
| |
| result = tmp108_reg_write(dev, TI_TMP108_REG_CONF, config); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| int ti_tmp108_read_temp(const struct device *dev) |
| { |
| struct tmp108_data *drv_data = dev->data; |
| int result; |
| |
| /* clear previous temperature readings */ |
| |
| drv_data->sample = 0U; |
| |
| /* Get the most recent temperature measurement */ |
| |
| result = tmp108_reg_read(dev, TI_TMP108_REG_TEMP, &drv_data->sample); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| static int tmp108_sample_fetch(const struct device *dev, |
| enum sensor_channel chan) |
| { |
| struct tmp108_data *drv_data = dev->data; |
| int result; |
| |
| if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_AMBIENT_TEMP) { |
| return -ENOTSUP; |
| } |
| |
| /* If one shot mode is set, query chip for reading |
| * should be finished 30 ms later |
| */ |
| if (drv_data->one_shot_mode == true) { |
| |
| result = tmp108_write_config(dev, |
| TI_TMP108_MODE_MASK(dev), |
| TI_TMP108_MODE_ONE_SHOT(dev)); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| /* Schedule read to start in 30 ms if mode change was successful |
| * the typical wakeup time given in the data sheet is 27 |
| */ |
| result = k_work_schedule(&drv_data->scheduled_work, |
| K_MSEC(TMP108_WAKEUP_TIME_IN_MS(dev))); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| result = ti_tmp108_read_temp(dev); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| static int tmp108_channel_get(const struct device *dev, |
| enum sensor_channel chan, |
| struct sensor_value *val) |
| { |
| struct tmp108_data *drv_data = dev->data; |
| int32_t uval; |
| |
| if (chan != SENSOR_CHAN_AMBIENT_TEMP) { |
| return -ENOTSUP; |
| } |
| |
| uval = ((int32_t)drv_data->sample * TMP108_TEMP_MULTIPLIER(dev)) / TMP108_TEMP_DIVISOR(dev); |
| val->val1 = uval / 1000000; |
| val->val2 = uval % 1000000; |
| |
| return 0; |
| } |
| |
| static int tmp108_attr_get(const struct device *dev, |
| enum sensor_channel chan, |
| enum sensor_attribute attr, |
| struct sensor_value *val) |
| { |
| int result; |
| uint16_t tmp_val; |
| |
| if (chan != SENSOR_CHAN_AMBIENT_TEMP && chan != SENSOR_CHAN_ALL) { |
| return -ENOTSUP; |
| } |
| |
| switch ((int) attr) { |
| case SENSOR_ATTR_CONFIGURATION: |
| result = tmp108_reg_read(dev, |
| TI_TMP108_REG_CONF, |
| &tmp_val); |
| val->val1 = tmp_val; |
| val->val2 = 0; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| return result; |
| } |
| |
| static int tmp108_attr_set(const struct device *dev, |
| enum sensor_channel chan, |
| enum sensor_attribute attr, |
| const struct sensor_value *val) |
| { |
| struct tmp108_data *drv_data = dev->data; |
| uint16_t mode = 0; |
| uint16_t reg_value = 0; |
| int result = 0; |
| int32_t uval; |
| |
| if (chan != SENSOR_CHAN_AMBIENT_TEMP && chan != SENSOR_CHAN_ALL) { |
| return -ENOTSUP; |
| } |
| |
| switch ((int) attr) { |
| case SENSOR_ATTR_HYSTERESIS: |
| if (TI_TMP108_HYSTER_0_C(dev) == TI_TMP108_CONF_NA) { |
| LOG_WRN("AS621x Series lacks Hysterisis setttings"); |
| return -ENOTSUP; |
| } |
| if (val->val1 < 1) { |
| mode = TI_TMP108_HYSTER_0_C(dev); |
| } else if (val->val1 < 2) { |
| mode = TI_TMP108_HYSTER_1_C(dev); |
| } else if (val->val1 < 4) { |
| mode = TI_TMP108_HYSTER_2_C(dev); |
| } else { |
| mode = TI_TMP108_HYSTER_4_C(dev); |
| } |
| |
| result = tmp108_write_config(dev, |
| TI_TMP108_HYSTER_MASK(dev), |
| mode); |
| break; |
| |
| case SENSOR_ATTR_ALERT: |
| /* Spec Sheet Errata: TM is set on reset not cleared */ |
| if (val->val1 == 1) { |
| mode = TI_TMP108_CONF_TM_INT(dev); |
| } else { |
| mode = TI_TMP108_CONF_TM_CMP(dev); |
| } |
| |
| result = tmp108_write_config(dev, |
| TI_TMP108_CONF_TM_MASK(dev), |
| mode); |
| break; |
| |
| case SENSOR_ATTR_LOWER_THRESH: |
| uval = val->val1 * 1000000 + val->val2; |
| reg_value = (uval * TMP108_TEMP_DIVISOR(dev)) / TMP108_TEMP_MULTIPLIER(dev); |
| result = tmp108_reg_write(dev, |
| TI_TMP108_REG_LOW_LIMIT, |
| reg_value); |
| break; |
| |
| case SENSOR_ATTR_UPPER_THRESH: |
| uval = val->val1 * 1000000 + val->val2; |
| reg_value = (uval * TMP108_TEMP_DIVISOR(dev)) / TMP108_TEMP_MULTIPLIER(dev); |
| result = tmp108_reg_write(dev, |
| TI_TMP108_REG_HIGH_LIMIT, |
| reg_value); |
| break; |
| |
| case SENSOR_ATTR_SAMPLING_FREQUENCY: |
| if (val->val1 < 1) { |
| mode = TI_TMP108_FREQ_4_SECS(dev); |
| } else if (val->val1 < 4) { |
| mode = TI_TMP108_FREQ_1_HZ(dev); |
| } else if (val->val1 < 16) { |
| mode = TI_TMP108_FREQ_4_HZ(dev); |
| } else { |
| mode = TI_TMP108_FREQ_16_HZ(dev); |
| } |
| result = tmp108_write_config(dev, |
| TI_TMP108_FREQ_MASK(dev), |
| mode); |
| break; |
| |
| case SENSOR_ATTR_TMP108_SHUTDOWN_MODE: |
| result = tmp108_write_config(dev, |
| TI_TMP108_MODE_MASK(dev), |
| TI_TMP108_MODE_SHUTDOWN(dev)); |
| drv_data->one_shot_mode = false; |
| break; |
| |
| case SENSOR_ATTR_TMP108_CONTINUOUS_CONVERSION_MODE: |
| result = tmp108_write_config(dev, |
| TI_TMP108_MODE_MASK(dev), |
| TI_TMP108_MODE_CONTINUOUS(dev)); |
| drv_data->one_shot_mode = false; |
| break; |
| |
| case SENSOR_ATTR_TMP108_ONE_SHOT_MODE: |
| result = tmp108_write_config(dev, |
| TI_TMP108_MODE_MASK(dev), |
| TI_TMP108_MODE_ONE_SHOT(dev)); |
| drv_data->one_shot_mode = true; |
| break; |
| |
| case SENSOR_ATTR_TMP108_ALERT_POLARITY: |
| if (val->val1 == 1) { |
| mode = TI_TMP108_CONF_POL_HIGH(dev); |
| } else { |
| mode = TI_TMP108_CONF_POL_LOW(dev); |
| } |
| result = tmp108_write_config(dev, |
| TI_TMP108_CONF_POL_MASK(dev), |
| mode); |
| break; |
| |
| default: |
| return -ENOTSUP; |
| } |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| return 0; |
| } |
| |
| static const struct sensor_driver_api tmp108_driver_api = { |
| .attr_set = tmp108_attr_set, |
| .attr_get = tmp108_attr_get, |
| .sample_fetch = tmp108_sample_fetch, |
| .channel_get = tmp108_channel_get, |
| .trigger_set = tmp_108_trigger_set, |
| }; |
| |
| #ifdef CONFIG_TMP108_ALERT_INTERRUPTS |
| static int setup_interrupts(const struct device *dev) |
| { |
| struct tmp108_data *drv_data = dev->data; |
| const struct tmp108_config *config = dev->config; |
| const struct gpio_dt_spec *alert_gpio = &config->alert_gpio; |
| int result; |
| |
| if (!device_is_ready(alert_gpio->port)) { |
| LOG_ERR("tmp108: gpio controller %s not ready", |
| alert_gpio->port->name); |
| return -ENODEV; |
| } |
| |
| result = gpio_pin_configure_dt(alert_gpio, GPIO_INPUT); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| gpio_init_callback(&drv_data->temp_alert_gpio_cb, |
| tmp108_trigger_handle_alert, |
| BIT(alert_gpio->pin)); |
| |
| result = gpio_add_callback(alert_gpio->port, |
| &drv_data->temp_alert_gpio_cb); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| result = gpio_pin_interrupt_configure_dt(alert_gpio, |
| GPIO_INT_EDGE_BOTH); |
| |
| if (result < 0) { |
| return result; |
| } |
| |
| return 0; |
| } |
| #endif |
| |
| static int tmp108_init(const struct device *dev) |
| { |
| const struct tmp108_config *cfg = dev->config; |
| struct tmp108_data *drv_data = dev->data; |
| int result = 0; |
| |
| if (!device_is_ready(cfg->i2c_spec.bus)) { |
| LOG_ERR("I2C dev %s not ready", cfg->i2c_spec.bus->name); |
| return -ENODEV; |
| } |
| |
| drv_data->scheduled_work.work.handler = tmp108_trigger_handle_one_shot; |
| |
| /* save this driver instance for passing to other functions */ |
| drv_data->tmp108_dev = dev; |
| |
| #ifdef CONFIG_TMP108_ALERT_INTERRUPTS |
| result = setup_interrupts(dev); |
| |
| if (result < 0) { |
| return result; |
| } |
| #endif |
| /* clear and set configuration registers back to default values */ |
| result = tmp108_write_config(dev, |
| 0x0000, |
| TMP108_CONF_RST(dev)); |
| return result; |
| } |
| |
| #define TMP108_DEFINE(inst, t) \ |
| static struct tmp108_data tmp108_prv_data_##inst##t; \ |
| static const struct tmp108_config tmp108_config_##inst##t = { \ |
| .i2c_spec = I2C_DT_SPEC_INST_GET(inst), \ |
| .alert_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, \ |
| alert_gpios, { 0 }),\ |
| .reg_def = t##_CONF \ |
| }; \ |
| SENSOR_DEVICE_DT_INST_DEFINE(inst, \ |
| &tmp108_init, \ |
| NULL, \ |
| &tmp108_prv_data_##inst##t, \ |
| &tmp108_config_##inst##t, \ |
| POST_KERNEL, \ |
| CONFIG_SENSOR_INIT_PRIORITY, \ |
| &tmp108_driver_api); |
| |
| #define TMP108_INIT(n) TMP108_DEFINE(n, TI_TMP108) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_tmp108 |
| DT_INST_FOREACH_STATUS_OKAY(TMP108_INIT) |
| |
| #define AS6212_INIT(n) TMP108_DEFINE(n, AMS_AS6212) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ams_as6212 |
| DT_INST_FOREACH_STATUS_OKAY(AS6212_INIT) |