| /* |
| * 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) |