blob: e6fdefea24a192700fbae8315b50e45d1a374ef1 [file] [log] [blame]
/*
* Copyright (c) 2020 Vestas Wind Systems A/S
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nxp_kinetis_temperature
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(temp_kinetis, CONFIG_SENSOR_LOG_LEVEL);
/*
* Driver assumptions:
* - ADC samples are in uint16_t format
* - Both ADC channels (sensor and bandgap) are on the same ADC instance
*
* See NXP Application Note AN3031 for details on calculations.
*/
/* Two ADC samples required for each reading, sensor value and bandgap value */
#define TEMP_KINETIS_ADC_SAMPLES 2
struct temp_kinetis_config {
const struct device *adc;
uint8_t sensor_adc_ch;
uint8_t bandgap_adc_ch;
int bandgap_mv;
int vtemp25_mv;
int slope_cold_uv;
int slope_hot_uv;
struct adc_sequence adc_seq;
};
struct temp_kinetis_data {
uint16_t buffer[TEMP_KINETIS_ADC_SAMPLES];
};
static int temp_kinetis_sample_fetch(const struct device *dev,
enum sensor_channel chan)
{
const struct temp_kinetis_config *config = dev->config;
struct temp_kinetis_data *data = dev->data;
#ifdef CONFIG_TEMP_KINETIS_FILTER
uint16_t previous[TEMP_KINETIS_ADC_SAMPLES];
int i;
#endif /* CONFIG_TEMP_KINETIS_FILTER */
int err;
/* Always read both sensor and bandgap voltage in one go */
if (chan != SENSOR_CHAN_ALL && chan != SENSOR_CHAN_DIE_TEMP &&
chan != SENSOR_CHAN_VOLTAGE) {
return -ENOTSUP;
}
#ifdef CONFIG_TEMP_KINETIS_FILTER
memcpy(previous, data->buffer, sizeof(previous));
#endif /* CONFIG_TEMP_KINETIS_FILTER */
err = adc_read(config->adc, &config->adc_seq);
if (err) {
LOG_ERR("failed to read ADC channels (err %d)", err);
return err;
}
LOG_DBG("sensor = %d, bandgap = %d", data->buffer[0], data->buffer[1]);
#ifdef CONFIG_TEMP_KINETIS_FILTER
if (previous[0] != 0 && previous[1] != 0) {
for (i = 0; i < ARRAY_SIZE(previous); i++) {
data->buffer[i] = (data->buffer[i] >> 1) +
(previous[i] >> 1);
}
LOG_DBG("sensor = %d, bandgap = %d (filtered)", data->buffer[0],
data->buffer[1]);
}
#endif /* CONFIG_TEMP_KINETIS_FILTER */
return 0;
}
static int temp_kinetis_channel_get(const struct device *dev,
enum sensor_channel chan,
struct sensor_value *val)
{
const struct temp_kinetis_config *config = dev->config;
struct temp_kinetis_data *data = dev->data;
uint16_t adcr_vdd = BIT_MASK(config->adc_seq.resolution);
uint16_t adcr_temp25;
int32_t temp_cc;
int32_t vdd_mv;
int slope_uv;
uint16_t adcr_100m;
if (chan != SENSOR_CHAN_VOLTAGE && chan != SENSOR_CHAN_DIE_TEMP) {
return -ENOTSUP;
}
/* VDD (or VREF, but AN3031 calls it VDD) in millivolts */
vdd_mv = (adcr_vdd * config->bandgap_mv) / data->buffer[1];
if (chan == SENSOR_CHAN_VOLTAGE) {
val->val1 = vdd_mv / 1000;
val->val2 = (vdd_mv % 1000) * 1000;
return 0;
}
/* ADC result for temperature = 25 degrees Celsius */
adcr_temp25 = (adcr_vdd * config->vtemp25_mv) / vdd_mv;
/* Determine which slope to use */
if (data->buffer[0] > adcr_temp25) {
slope_uv = config->slope_cold_uv;
} else {
slope_uv = config->slope_hot_uv;
}
adcr_100m = (adcr_vdd * slope_uv) / (vdd_mv * 10);
/* Temperature in centi degrees Celsius */
temp_cc = 2500 -
(((data->buffer[0] - adcr_temp25) * 10000) / adcr_100m);
val->val1 = temp_cc / 100;
val->val2 = (temp_cc % 100) * 10000;
return 0;
}
static const struct sensor_driver_api temp_kinetis_driver_api = {
.sample_fetch = temp_kinetis_sample_fetch,
.channel_get = temp_kinetis_channel_get,
};
static int temp_kinetis_init(const struct device *dev)
{
const struct temp_kinetis_config *config = dev->config;
struct temp_kinetis_data *data = dev->data;
int err;
int i;
const struct adc_channel_cfg ch_cfg[] = {
{
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = config->sensor_adc_ch,
.differential = 0,
},
{
.gain = ADC_GAIN_1,
.reference = ADC_REF_INTERNAL,
.acquisition_time = ADC_ACQ_TIME_DEFAULT,
.channel_id = config->bandgap_adc_ch,
.differential = 0,
},
};
memset(&data->buffer, 0, ARRAY_SIZE(data->buffer));
if (!device_is_ready(config->adc)) {
LOG_ERR("ADC device is not ready");
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(ch_cfg); i++) {
err = adc_channel_setup(config->adc, &ch_cfg[i]);
if (err) {
LOG_ERR("failed to configure ADC channel (err %d)",
err);
return err;
}
}
return 0;
}
BUILD_ASSERT(DT_NUM_INST_STATUS_OKAY(DT_DRV_COMPAT) <= 1,
"unsupported temp instance");
#define TEMP_KINETIS_INIT(inst) \
BUILD_ASSERT(DT_INST_IO_CHANNELS_INPUT_BY_NAME(inst, sensor) < \
DT_INST_IO_CHANNELS_INPUT_BY_NAME(inst, bandgap), \
"This driver assumes sensor ADC channel to come before "\
"bandgap ADC channel"); \
\
static struct temp_kinetis_data temp_kinetis_data_0; \
\
static const struct temp_kinetis_config temp_kinetis_config_0 = {\
.adc = DEVICE_DT_GET(DT_INST_IO_CHANNELS_CTLR(inst)),\
.sensor_adc_ch = \
DT_INST_IO_CHANNELS_INPUT_BY_NAME(inst, sensor),\
.bandgap_adc_ch = \
DT_INST_IO_CHANNELS_INPUT_BY_NAME(inst, bandgap),\
.bandgap_mv = DT_INST_PROP(0, bandgap_voltage) / 1000, \
.vtemp25_mv = DT_INST_PROP(0, vtemp25) / 1000, \
.slope_cold_uv = DT_INST_PROP(0, sensor_slope_cold), \
.slope_hot_uv = DT_INST_PROP(0, sensor_slope_hot), \
.adc_seq = { \
.options = NULL, \
.channels = \
BIT(DT_INST_IO_CHANNELS_INPUT_BY_NAME(inst, sensor)) | \
BIT(DT_INST_IO_CHANNELS_INPUT_BY_NAME(inst, bandgap)), \
.buffer = &temp_kinetis_data_0.buffer, \
.buffer_size = sizeof(temp_kinetis_data_0.buffer),\
.resolution = CONFIG_TEMP_KINETIS_RESOLUTION, \
.oversampling = CONFIG_TEMP_KINETIS_OVERSAMPLING,\
.calibrate = false, \
}, \
}; \
\
DEVICE_DT_INST_DEFINE(inst, temp_kinetis_init, \
NULL, \
&temp_kinetis_data_0, \
&temp_kinetis_config_0, POST_KERNEL, \
CONFIG_SENSOR_INIT_PRIORITY, \
&temp_kinetis_driver_api);
DT_INST_FOREACH_STATUS_OKAY(TEMP_KINETIS_INIT)