/*
* Copyright (c) 2021 Jimmy Johnson <catch22@fastmail.net>
*
* 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);
/** TI conversion scale from 16 bit int temp value to float */
#define TMP108_TEMP_MULTIPLIER 62500
/** TMP typical conversion time of 27 ms after waking from sleep */
#define TMP108_WAKEUP_TIME_IN_MS 30
struct tmp108_config {
const struct i2c_dt_spec i2c_spec;
const struct gpio_dt_spec alert_gpio;
};
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,
TI_TMP108_MODE_ONE_SHOT);
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));
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 >> 4U) * TMP108_TEMP_MULTIPLIER;
val->val1 = uval / 1000000U;
val->val2 = uval % 1000000U;
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;
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,
(uint16_t *) &(val->val1));
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;
if (chan != SENSOR_CHAN_AMBIENT_TEMP && chan != SENSOR_CHAN_ALL) {
return -ENOTSUP;
}
switch ((int) attr) {
case SENSOR_ATTR_HYSTERESIS:
if (val->val1 < 1) {
mode = TI_TMP108_HYSTER_0_C;
} else if (val->val1 < 2) {
mode = TI_TMP108_HYSTER_1_C;
} else if (val->val1 < 4) {
mode = TI_TMP108_HYSTER_2_C;
} else {
mode = TI_TMP108_HYSTER_4_C;
}
result = tmp108_write_config(dev,
TI_TMP108_HYSTER_MASK,
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;
} else {
mode = TI_TMP108_CONF_TM_CMP;
}
result = tmp108_write_config(dev,
TI_TMP108_CONF_TM_MASK,
mode);
break;
case SENSOR_ATTR_LOWER_THRESH:
reg_value = (val->val1 << 8) | (0x00FF & val->val2);
result = tmp108_reg_write(dev,
TI_TMP108_REG_LOW_LIMIT,
reg_value);
break;
case SENSOR_ATTR_UPPER_THRESH:
reg_value = (val->val1 << 8) | (0x00FF & val->val2);
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;
} else if (val->val1 < 4) {
mode = TI_TMP108_FREQ_1_HZ;
} else if (val->val1 < 16) {
mode = TI_TMP108_FREQ_4_HZ;
} else {
mode = TI_TMP108_FREQ_16_HZ;
}
result = tmp108_write_config(dev,
TI_TMP108_FREQ_MASK,
mode);
break;
case SENSOR_ATTR_TMP108_SHUTDOWN_MODE:
result = tmp108_write_config(dev,
TI_TMP108_MODE_MASK,
TI_TMP108_MODE_SHUTDOWN);
drv_data->one_shot_mode = false;
break;
case SENSOR_ATTR_TMP108_CONTINUOUS_CONVERSION_MODE:
result = tmp108_write_config(dev,
TI_TMP108_MODE_MASK,
TI_TMP108_MODE_CONTINUOUS);
drv_data->one_shot_mode = false;
break;
case SENSOR_ATTR_TMP108_ONE_SHOT_MODE:
result = tmp108_write_config(dev,
TI_TMP108_MODE_MASK,
TI_TMP108_MODE_ONE_SHOT);
drv_data->one_shot_mode = true;
break;
case SENSOR_ATTR_TMP108_ALERT_POLARITY:
if (val->val1 == 1) {
mode = TI_TMP108_CONF_POL_HIGH;
} else {
mode = TI_TMP108_CONF_POL_LOW;
}
result = tmp108_write_config(dev,
TI_TMP108_CONF_POL_MASK,
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);
#endif
return result;
}
#define TMP108_DEFINE(inst) \
static struct tmp108_data tmp108_prv_data_##inst; \
static const struct tmp108_config tmp108_config_##inst = { \
.i2c_spec = I2C_DT_SPEC_INST_GET(inst), \
.alert_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, \
alert_gpios, { 0 }) \
}; \
DEVICE_DT_INST_DEFINE(inst, \
&tmp108_init, \
NULL, \
&tmp108_prv_data_##inst, \
&tmp108_config_##inst, \
POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&tmp108_driver_api);
DT_INST_FOREACH_STATUS_OKAY(TMP108_DEFINE)