| /* |
| * Copyright (c) 2019 Vestas Wind Systems A/S |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| /** |
| * @file |
| * @brief ADC driver for the LMP90xxx AFE. |
| */ |
| |
| #include <zephyr/drivers/adc.h> |
| #include <zephyr/drivers/adc/lmp90xxx.h> |
| #include <zephyr/drivers/gpio.h> |
| #include <zephyr/drivers/spi.h> |
| #include <zephyr/kernel.h> |
| #include <zephyr/sys/byteorder.h> |
| #include <zephyr/sys/crc.h> |
| |
| #define LOG_LEVEL CONFIG_ADC_LOG_LEVEL |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(adc_lmp90xxx); |
| |
| #define ADC_CONTEXT_USES_KERNEL_TIMER |
| #include "adc_context.h" |
| |
| /* LMP90xxx register addresses */ |
| #define LMP90XXX_REG_RESETCN 0x00U |
| #define LMP90XXX_REG_SPI_HANDSHAKECN 0x01U |
| #define LMP90XXX_REG_SPI_RESET 0x02U |
| #define LMP90XXX_REG_SPI_STREAMCN 0x03U |
| #define LMP90XXX_REG_PWRCN 0x08U |
| #define LMP90XXX_REG_DATA_ONLY_1 0x09U |
| #define LMP90XXX_REG_DATA_ONLY_2 0x0AU |
| #define LMP90XXX_REG_ADC_RESTART 0x0BU |
| #define LMP90XXX_REG_GPIO_DIRCN 0x0EU |
| #define LMP90XXX_REG_GPIO_DAT 0x0FU |
| #define LMP90XXX_REG_BGCALCN 0x10U |
| #define LMP90XXX_REG_SPI_DRDYBCN 0x11U |
| #define LMP90XXX_REG_ADC_AUXCN 0x12U |
| #define LMP90XXX_REG_SPI_CRC_CN 0x13U |
| #define LMP90XXX_REG_SENDIAG_THLDH 0x14U |
| #define LMP90XXX_REG_SENDIAG_THLDL 0x15U |
| #define LMP90XXX_REG_SCALCN 0x17U |
| #define LMP90XXX_REG_ADC_DONE 0x18U |
| #define LMP90XXX_REG_SENDIAG_FLAGS 0x19U |
| #define LMP90XXX_REG_ADC_DOUT 0x1AU |
| #define LMP90XXX_REG_SPI_CRC_DAT 0x1DU |
| #define LMP90XXX_REG_CH_STS 0x1EU |
| #define LMP90XXX_REG_CH_SCAN 0x1FU |
| |
| /* LMP90xxx channel input and configuration registers */ |
| #define LMP90XXX_REG_CH_INPUTCN(ch) (0x20U + (2 * ch)) |
| #define LMP90XXX_REG_CH_CONFIG(ch) (0x21U + (2 * ch)) |
| |
| /* LMP90xxx upper (URA) and lower (LRA) register addresses */ |
| #define LMP90XXX_URA(addr) ((addr >> 4U) & GENMASK(2, 0)) |
| #define LMP90XXX_LRA(addr) (addr & GENMASK(3, 0)) |
| |
| /* LMP90xxx instruction byte 1 (INST1) */ |
| #define LMP90XXX_INST1_WAB 0x10U |
| #define LMP90XXX_INST1_RA 0x90U |
| |
| /* LMP90xxx instruction byte 2 (INST2) */ |
| #define LMP90XXX_INST2_WB 0U |
| #define LMP90XXX_INST2_R BIT(7) |
| #define LMP90XXX_INST2_SZ_1 (0x0U << 5) |
| #define LMP90XXX_INST2_SZ_2 (0x1U << 5) |
| #define LMP90XXX_INST2_SZ_3 (0x2U << 5) |
| #define LMP90XXX_INST2_SZ_STREAM (0x3U << 5) |
| |
| /* LMP90xxx register values/commands */ |
| #define LMP90XXX_REG_AND_CNV_RST 0xC3U |
| #define LMP90XXX_SDO_DRDYB_DRIVER(x) ((x & BIT_MASK(3)) << 1) |
| #define LMP90XXX_PWRCN(x) (x & BIT_MASK(2)) |
| #define LMP90XXX_RTD_CUR_SEL(x) (x & BIT_MASK(4)) |
| #define LMP90XXX_SPI_DRDYB_D6(x) ((x & BIT(0)) << 7) |
| #define LMP90XXX_EN_CRC(x) ((x & BIT(0)) << 4) |
| #define LMP90XXX_DRDYB_AFT_CRC(x) ((x & BIT(0)) << 2) |
| #define LMP90XXX_CH_SCAN_SEL(x) ((x & BIT_MASK(2)) << 6) |
| #define LMP90XXX_LAST_CH(x) ((x & BIT_MASK(3)) << 3) |
| #define LMP90XXX_FIRST_CH(x) (x & BIT_MASK(3)) |
| #define LMP90XXX_BURNOUT_EN(x) ((x & BIT(0)) << 7) |
| #define LMP90XXX_VREF_SEL(x) ((x & BIT(0)) << 6) |
| #define LMP90XXX_VINP(x) ((x & BIT_MASK(3)) << 3) |
| #define LMP90XXX_VINN(x) (x & BIT_MASK(3)) |
| #define LMP90XXX_BGCALN(x) (x & BIT_MASK(3)) |
| #define LMP90XXX_ODR_SEL(x) ((x & BIT_MASK(3)) << 4) |
| #define LMP90XXX_GAIN_SEL(x) ((x & BIT_MASK(3)) << 1) |
| #define LMP90XXX_BUF_EN(x) (x & BIT(0)) |
| #define LMP90XXX_GPIO_DAT_MASK BIT_MASK(LMP90XXX_GPIO_MAX) |
| |
| /* Invalid (never used) Upper Register Address */ |
| #define LMP90XXX_INVALID_URA UINT8_MAX |
| |
| /* Maximum number of ADC channels */ |
| #define LMP90XXX_MAX_CHANNELS 7 |
| |
| /* Maximum number of ADC inputs */ |
| #define LMP90XXX_MAX_INPUTS 8 |
| |
| /* Default Output Data Rate (ODR) is 214.65 SPS */ |
| #define LMP90XXX_DEFAULT_ODR 7 |
| |
| /* Macro for checking if Data Ready Bar IRQ is in use */ |
| #define LMP90XXX_HAS_DRDYB(config) (config->drdyb.port != NULL) |
| |
| struct lmp90xxx_config { |
| struct spi_dt_spec bus; |
| struct gpio_dt_spec drdyb; |
| uint8_t rtd_current; |
| uint8_t resolution; |
| uint8_t channels; |
| }; |
| |
| struct lmp90xxx_data { |
| struct adc_context ctx; |
| const struct device *dev; |
| struct gpio_callback drdyb_cb; |
| struct k_mutex ura_lock; |
| uint8_t ura; |
| int32_t *buffer; |
| int32_t *repeat_buffer; |
| uint32_t channels; |
| bool calibrate; |
| uint8_t channel_odr[LMP90XXX_MAX_CHANNELS]; |
| #ifdef CONFIG_ADC_LMP90XXX_GPIO |
| struct k_mutex gpio_lock; |
| uint8_t gpio_dircn; |
| uint8_t gpio_dat; |
| #endif /* CONFIG_ADC_LMP90XXX_GPIO */ |
| struct k_thread thread; |
| struct k_sem acq_sem; |
| struct k_sem drdyb_sem; |
| |
| K_KERNEL_STACK_MEMBER(stack, |
| CONFIG_ADC_LMP90XXX_ACQUISITION_THREAD_STACK_SIZE); |
| }; |
| |
| /* |
| * Approximated LMP90xxx acquisition times in milliseconds. These are |
| * used for the initial delay when polling for data ready. |
| */ |
| static const int32_t lmp90xxx_odr_delay_tbl[8] = { |
| 596, /* 13.42/8 = 1.6775 SPS */ |
| 298, /* 13.42/4 = 3.355 SPS */ |
| 149, /* 13.42/2 = 6.71 SPS */ |
| 75, /* 13.42 SPS */ |
| 37, /* 214.65/8 = 26.83125 SPS */ |
| 19, /* 214.65/4 = 53.6625 SPS */ |
| 9, /* 214.65/2 = 107.325 SPS */ |
| 5, /* 214.65 SPS (default) */ |
| }; |
| |
| static inline uint8_t lmp90xxx_inst2_sz(size_t len) |
| { |
| if (len == 1) { |
| return LMP90XXX_INST2_SZ_1; |
| } else if (len == 2) { |
| return LMP90XXX_INST2_SZ_2; |
| } else if (len == 3) { |
| return LMP90XXX_INST2_SZ_3; |
| } else { |
| return LMP90XXX_INST2_SZ_STREAM; |
| } |
| } |
| |
| static int lmp90xxx_read_reg(const struct device *dev, uint8_t addr, |
| uint8_t *dptr, |
| size_t len) |
| { |
| const struct lmp90xxx_config *config = dev->config; |
| struct lmp90xxx_data *data = dev->data; |
| uint8_t ura = LMP90XXX_URA(addr); |
| uint8_t inst1_uab[2] = { LMP90XXX_INST1_WAB, ura }; |
| uint8_t inst2 = LMP90XXX_INST2_R | LMP90XXX_LRA(addr); |
| struct spi_buf tx_buf[2]; |
| struct spi_buf rx_buf[2]; |
| struct spi_buf_set tx; |
| struct spi_buf_set rx; |
| int dummy = 0; |
| int i = 0; |
| int err; |
| |
| if (len == 0) { |
| LOG_ERR("attempt to read 0 bytes from register 0x%02x", addr); |
| return -EINVAL; |
| } |
| |
| if (k_is_in_isr()) { |
| /* Prevent SPI transactions from an ISR */ |
| return -EWOULDBLOCK; |
| } |
| |
| k_mutex_lock(&data->ura_lock, K_FOREVER); |
| |
| if (ura != data->ura) { |
| /* Instruction Byte 1 + Upper Address Byte */ |
| tx_buf[i].buf = inst1_uab; |
| tx_buf[i].len = sizeof(inst1_uab); |
| dummy += sizeof(inst1_uab); |
| i++; |
| } |
| |
| /* Instruction Byte 2 */ |
| inst2 |= lmp90xxx_inst2_sz(len); |
| tx_buf[i].buf = &inst2; |
| tx_buf[i].len = sizeof(inst2); |
| dummy += sizeof(inst2); |
| i++; |
| |
| /* Dummy RX Bytes */ |
| rx_buf[0].buf = NULL; |
| rx_buf[0].len = dummy; |
| |
| /* Data Byte(s) */ |
| rx_buf[1].buf = dptr; |
| rx_buf[1].len = len; |
| |
| tx.buffers = tx_buf; |
| tx.count = i; |
| rx.buffers = rx_buf; |
| rx.count = 2; |
| |
| err = spi_transceive_dt(&config->bus, &tx, &rx); |
| if (!err) { |
| data->ura = ura; |
| } else { |
| /* Force INST1 + UAB on next access */ |
| data->ura = LMP90XXX_INVALID_URA; |
| } |
| |
| k_mutex_unlock(&data->ura_lock); |
| |
| return err; |
| } |
| |
| static int lmp90xxx_read_reg8(const struct device *dev, uint8_t addr, |
| uint8_t *val) |
| { |
| return lmp90xxx_read_reg(dev, addr, val, sizeof(*val)); |
| } |
| |
| static int lmp90xxx_write_reg(const struct device *dev, uint8_t addr, |
| uint8_t *dptr, |
| size_t len) |
| { |
| const struct lmp90xxx_config *config = dev->config; |
| struct lmp90xxx_data *data = dev->data; |
| uint8_t ura = LMP90XXX_URA(addr); |
| uint8_t inst1_uab[2] = { LMP90XXX_INST1_WAB, ura }; |
| uint8_t inst2 = LMP90XXX_INST2_WB | LMP90XXX_LRA(addr); |
| struct spi_buf tx_buf[3]; |
| struct spi_buf_set tx; |
| int i = 0; |
| int err; |
| |
| if (len == 0) { |
| LOG_ERR("attempt write 0 bytes to register 0x%02x", addr); |
| return -EINVAL; |
| } |
| |
| if (k_is_in_isr()) { |
| /* Prevent SPI transactions from an ISR */ |
| return -EWOULDBLOCK; |
| } |
| |
| k_mutex_lock(&data->ura_lock, K_FOREVER); |
| |
| if (ura != data->ura) { |
| /* Instruction Byte 1 + Upper Address Byte */ |
| tx_buf[i].buf = inst1_uab; |
| tx_buf[i].len = sizeof(inst1_uab); |
| i++; |
| } |
| |
| /* Instruction Byte 2 */ |
| inst2 |= lmp90xxx_inst2_sz(len); |
| tx_buf[i].buf = &inst2; |
| tx_buf[i].len = sizeof(inst2); |
| i++; |
| |
| /* Data Byte(s) */ |
| tx_buf[i].buf = dptr; |
| tx_buf[i].len = len; |
| i++; |
| |
| tx.buffers = tx_buf; |
| tx.count = i; |
| |
| err = spi_write_dt(&config->bus, &tx); |
| if (!err) { |
| data->ura = ura; |
| } else { |
| /* Force INST1 + UAB on next access */ |
| data->ura = LMP90XXX_INVALID_URA; |
| } |
| |
| k_mutex_unlock(&data->ura_lock); |
| |
| return err; |
| } |
| |
| static int lmp90xxx_write_reg8(const struct device *dev, uint8_t addr, |
| uint8_t val) |
| { |
| return lmp90xxx_write_reg(dev, addr, &val, sizeof(val)); |
| } |
| |
| static int lmp90xxx_soft_reset(const struct device *dev) |
| { |
| int err; |
| |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_RESETCN, |
| LMP90XXX_REG_AND_CNV_RST); |
| if (err) { |
| return err; |
| } |
| |
| /* Write to RESETCN twice in order to reset mode as well as registers */ |
| return lmp90xxx_write_reg8(dev, LMP90XXX_REG_RESETCN, |
| LMP90XXX_REG_AND_CNV_RST); |
| } |
| |
| static inline bool lmp90xxx_has_channel(const struct device *dev, |
| uint8_t channel) |
| { |
| const struct lmp90xxx_config *config = dev->config; |
| |
| if (channel >= config->channels) { |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| static inline bool lmp90xxx_has_input(const struct device *dev, uint8_t input) |
| { |
| const struct lmp90xxx_config *config = dev->config; |
| |
| if (input >= LMP90XXX_MAX_INPUTS) { |
| return false; |
| } else if (config->channels < LMP90XXX_MAX_CHANNELS && |
| (input >= 3 && input <= 5)) { |
| /* This device only has inputs 0, 1, 2, 6, and 7 */ |
| return false; |
| } else { |
| return true; |
| } |
| } |
| |
| static inline int lmp90xxx_acq_time_to_odr(uint16_t acq_time) |
| { |
| uint16_t acq_value; |
| |
| if (acq_time == ADC_ACQ_TIME_DEFAULT) { |
| return LMP90XXX_DEFAULT_ODR; |
| } |
| |
| if (ADC_ACQ_TIME_UNIT(acq_time) != ADC_ACQ_TIME_TICKS) { |
| return -EINVAL; |
| } |
| |
| /* |
| * The LMP90xxx supports odd (and very slow) output data |
| * rates. Allow the caller to specify the ODR directly using |
| * ADC_ACQ_TIME_TICKS |
| */ |
| acq_value = ADC_ACQ_TIME_VALUE(acq_time); |
| if (acq_value <= LMP90XXX_DEFAULT_ODR) { |
| return acq_value; |
| } |
| |
| return -EINVAL; |
| } |
| |
| static int lmp90xxx_adc_channel_setup(const struct device *dev, |
| const struct adc_channel_cfg *channel_cfg) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| uint8_t chx_inputcn = LMP90XXX_BURNOUT_EN(0); /* No burnout currents */ |
| uint8_t chx_config = LMP90XXX_BUF_EN(0); /* No buffer */ |
| uint8_t payload[2]; |
| uint8_t addr; |
| int ret; |
| |
| switch (channel_cfg->reference) { |
| case ADC_REF_EXTERNAL0: |
| chx_inputcn |= LMP90XXX_VREF_SEL(0); |
| break; |
| case ADC_REF_EXTERNAL1: |
| chx_inputcn |= LMP90XXX_VREF_SEL(1); |
| break; |
| default: |
| LOG_ERR("unsupported channel reference type '%d'", |
| channel_cfg->reference); |
| return -ENOTSUP; |
| } |
| |
| if (!lmp90xxx_has_channel(dev, channel_cfg->channel_id)) { |
| LOG_ERR("unsupported channel id '%d'", channel_cfg->channel_id); |
| return -ENOTSUP; |
| } |
| |
| if (!lmp90xxx_has_input(dev, channel_cfg->input_positive)) { |
| LOG_ERR("unsupported positive input '%d'", |
| channel_cfg->input_positive); |
| return -ENOTSUP; |
| } |
| chx_inputcn |= LMP90XXX_VINP(channel_cfg->input_positive); |
| |
| if (!lmp90xxx_has_input(dev, channel_cfg->input_negative)) { |
| LOG_ERR("unsupported negative input '%d'", |
| channel_cfg->input_negative); |
| return -ENOTSUP; |
| } |
| chx_inputcn |= LMP90XXX_VINN(channel_cfg->input_negative); |
| |
| ret = lmp90xxx_acq_time_to_odr(channel_cfg->acquisition_time); |
| if (ret < 0) { |
| LOG_ERR("unsupported channel acquisition time 0x%02x", |
| channel_cfg->acquisition_time); |
| return -ENOTSUP; |
| } |
| chx_config |= LMP90XXX_ODR_SEL(ret); |
| data->channel_odr[channel_cfg->channel_id] = ret; |
| |
| switch (channel_cfg->gain) { |
| case ADC_GAIN_1: |
| chx_config |= LMP90XXX_GAIN_SEL(0); |
| break; |
| case ADC_GAIN_2: |
| chx_config |= LMP90XXX_GAIN_SEL(1); |
| break; |
| case ADC_GAIN_4: |
| chx_config |= LMP90XXX_GAIN_SEL(2); |
| break; |
| case ADC_GAIN_8: |
| chx_config |= LMP90XXX_GAIN_SEL(3); |
| break; |
| case ADC_GAIN_16: |
| chx_config |= LMP90XXX_GAIN_SEL(4); |
| break; |
| case ADC_GAIN_32: |
| chx_config |= LMP90XXX_GAIN_SEL(5); |
| break; |
| case ADC_GAIN_64: |
| chx_config |= LMP90XXX_GAIN_SEL(6); |
| break; |
| case ADC_GAIN_128: |
| chx_config |= LMP90XXX_GAIN_SEL(7); |
| break; |
| default: |
| LOG_ERR("unsupported channel gain '%d'", channel_cfg->gain); |
| return -ENOTSUP; |
| } |
| |
| payload[0] = chx_inputcn; |
| payload[1] = chx_config; |
| |
| addr = LMP90XXX_REG_CH_INPUTCN(channel_cfg->channel_id); |
| ret = lmp90xxx_write_reg(dev, addr, payload, sizeof(payload)); |
| if (ret) { |
| LOG_ERR("failed to configure channel (err %d)", ret); |
| } |
| |
| return ret; |
| } |
| |
| static int lmp90xxx_validate_buffer_size(const struct adc_sequence *sequence) |
| { |
| uint8_t channels = 0; |
| size_t needed; |
| uint32_t mask; |
| |
| for (mask = BIT(LMP90XXX_MAX_CHANNELS - 1); mask != 0; mask >>= 1) { |
| if (mask & sequence->channels) { |
| channels++; |
| } |
| } |
| |
| needed = channels * sizeof(int32_t); |
| if (sequence->options) { |
| needed *= (1 + sequence->options->extra_samplings); |
| } |
| |
| if (sequence->buffer_size < needed) { |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int lmp90xxx_adc_start_read(const struct device *dev, |
| const struct adc_sequence *sequence) |
| { |
| const struct lmp90xxx_config *config = dev->config; |
| struct lmp90xxx_data *data = dev->data; |
| int err; |
| |
| if (sequence->resolution != config->resolution) { |
| LOG_ERR("unsupported resolution %d", sequence->resolution); |
| return -ENOTSUP; |
| } |
| |
| if (!lmp90xxx_has_channel(dev, find_msb_set(sequence->channels) - 1)) { |
| LOG_ERR("unsupported channels in mask: 0x%08x", |
| sequence->channels); |
| return -ENOTSUP; |
| } |
| |
| err = lmp90xxx_validate_buffer_size(sequence); |
| if (err) { |
| LOG_ERR("buffer size too small"); |
| return err; |
| } |
| |
| data->buffer = sequence->buffer; |
| data->calibrate = sequence->calibrate; |
| adc_context_start_read(&data->ctx, sequence); |
| |
| return adc_context_wait_for_completion(&data->ctx); |
| } |
| |
| static int lmp90xxx_adc_read_async(const struct device *dev, |
| const struct adc_sequence *sequence, |
| struct k_poll_signal *async) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| int err; |
| |
| adc_context_lock(&data->ctx, async ? true : false, async); |
| err = lmp90xxx_adc_start_read(dev, sequence); |
| adc_context_release(&data->ctx, err); |
| |
| return err; |
| } |
| |
| static int lmp90xxx_adc_read(const struct device *dev, |
| const struct adc_sequence *sequence) |
| { |
| return lmp90xxx_adc_read_async(dev, sequence, NULL); |
| } |
| |
| static void adc_context_start_sampling(struct adc_context *ctx) |
| { |
| struct lmp90xxx_data *data = |
| CONTAINER_OF(ctx, struct lmp90xxx_data, ctx); |
| |
| data->channels = ctx->sequence.channels; |
| data->repeat_buffer = data->buffer; |
| |
| k_sem_give(&data->acq_sem); |
| } |
| |
| static void adc_context_update_buffer_pointer(struct adc_context *ctx, |
| bool repeat_sampling) |
| { |
| struct lmp90xxx_data *data = |
| CONTAINER_OF(ctx, struct lmp90xxx_data, ctx); |
| |
| if (repeat_sampling) { |
| data->buffer = data->repeat_buffer; |
| } |
| } |
| |
| static int lmp90xxx_adc_read_channel(const struct device *dev, |
| uint8_t channel, |
| int32_t *result) |
| { |
| const struct lmp90xxx_config *config = dev->config; |
| struct lmp90xxx_data *data = dev->data; |
| uint8_t adc_done; |
| uint8_t ch_scan; |
| uint8_t buf[4]; /* ADC_DOUT + CRC */ |
| int32_t delay; |
| uint8_t odr; |
| int err; |
| |
| /* Single channel, single scan mode */ |
| ch_scan = LMP90XXX_CH_SCAN_SEL(0x1) | LMP90XXX_FIRST_CH(channel) | |
| LMP90XXX_LAST_CH(channel); |
| |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_CH_SCAN, ch_scan); |
| if (err) { |
| LOG_ERR("failed to setup scan channels (err %d)", err); |
| return err; |
| } |
| |
| /* Start scan */ |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_PWRCN, LMP90XXX_PWRCN(0)); |
| if (err) { |
| LOG_ERR("failed to set active mode (err %d)", err); |
| return err; |
| } |
| |
| if (LMP90XXX_HAS_DRDYB(config)) { |
| k_sem_take(&data->drdyb_sem, K_FOREVER); |
| } else { |
| odr = data->channel_odr[channel]; |
| delay = lmp90xxx_odr_delay_tbl[odr]; |
| LOG_DBG("sleeping for %d ms", delay); |
| k_msleep(delay); |
| |
| /* Poll for data ready */ |
| do { |
| err = lmp90xxx_read_reg8(dev, LMP90XXX_REG_ADC_DONE, |
| &adc_done); |
| if (err) { |
| LOG_ERR("failed to read done (err %d)", err); |
| return err; |
| } |
| |
| if (adc_done == 0xFFU) { |
| LOG_DBG("sleeping for 1 ms"); |
| k_msleep(1); |
| } else { |
| break; |
| } |
| } while (true); |
| } |
| |
| if (IS_ENABLED(CONFIG_ADC_LMP90XXX_CRC)) { |
| err = lmp90xxx_read_reg(dev, LMP90XXX_REG_ADC_DOUT, buf, |
| sizeof(buf)); |
| } else { |
| err = lmp90xxx_read_reg(dev, LMP90XXX_REG_ADC_DOUT, buf, |
| config->resolution / 8); |
| } |
| |
| if (err) { |
| LOG_ERR("failed to read ADC DOUT (err %d)", err); |
| return err; |
| } |
| |
| if (IS_ENABLED(CONFIG_ADC_LMP90XXX_CRC)) { |
| uint8_t crc = crc8(buf, 3, 0x31, 0, false) ^ 0xFFU; |
| |
| if (buf[3] != crc) { |
| LOG_ERR("CRC mismatch (0x%02x vs. 0x%02x)", buf[3], |
| crc); |
| return -EIO; |
| } |
| } |
| |
| /* Read result, get rid of CRC, and sign extend result */ |
| *result = (int32_t)sys_get_be32(buf); |
| *result >>= (32 - config->resolution); |
| |
| return 0; |
| } |
| |
| static void lmp90xxx_acquisition_thread(void *p1, void *p2, void *p3) |
| { |
| ARG_UNUSED(p2); |
| ARG_UNUSED(p3); |
| |
| struct lmp90xxx_data *data = p1; |
| uint8_t bgcalcn = LMP90XXX_BGCALN(0x3); /* Default to BgCalMode3 */ |
| int32_t result = 0; |
| uint8_t channel; |
| int err; |
| |
| while (true) { |
| k_sem_take(&data->acq_sem, K_FOREVER); |
| |
| if (data->calibrate) { |
| /* Use BgCalMode2 */ |
| bgcalcn = LMP90XXX_BGCALN(0x2); |
| } |
| |
| LOG_DBG("using BGCALCN = 0x%02x", bgcalcn); |
| err = lmp90xxx_write_reg8(data->dev, |
| LMP90XXX_REG_BGCALCN, bgcalcn); |
| if (err) { |
| LOG_ERR("failed to setup background calibration " |
| "(err %d)", err); |
| adc_context_complete(&data->ctx, err); |
| break; |
| } |
| |
| while (data->channels) { |
| channel = find_lsb_set(data->channels) - 1; |
| |
| LOG_DBG("reading channel %d", channel); |
| |
| err = lmp90xxx_adc_read_channel(data->dev, |
| channel, &result); |
| if (err) { |
| adc_context_complete(&data->ctx, err); |
| break; |
| } |
| |
| LOG_DBG("finished channel %d, result = %d", channel, |
| result); |
| |
| /* |
| * ADC samples are stored as int32_t regardless of the |
| * resolution in order to provide a uniform interface |
| * for the driver. |
| */ |
| *data->buffer++ = result; |
| WRITE_BIT(data->channels, channel, 0); |
| } |
| |
| adc_context_on_sampling_done(&data->ctx, data->dev); |
| } |
| } |
| |
| static void lmp90xxx_drdyb_callback(const struct device *port, |
| struct gpio_callback *cb, uint32_t pins) |
| { |
| struct lmp90xxx_data *data = |
| CONTAINER_OF(cb, struct lmp90xxx_data, drdyb_cb); |
| |
| /* Signal thread that data is now ready */ |
| k_sem_give(&data->drdyb_sem); |
| } |
| |
| #ifdef CONFIG_ADC_LMP90XXX_GPIO |
| int lmp90xxx_gpio_set_output(const struct device *dev, uint8_t pin) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| int err = 0; |
| uint8_t tmp; |
| |
| if (pin > LMP90XXX_GPIO_MAX) { |
| return -EINVAL; |
| } |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| |
| tmp = data->gpio_dircn | BIT(pin); |
| if (tmp != data->gpio_dircn) { |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_GPIO_DIRCN, tmp); |
| if (!err) { |
| data->gpio_dircn = tmp; |
| } |
| } |
| |
| k_mutex_unlock(&data->gpio_lock); |
| |
| return err; |
| } |
| |
| int lmp90xxx_gpio_set_input(const struct device *dev, uint8_t pin) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| int err = 0; |
| uint8_t tmp; |
| |
| if (pin > LMP90XXX_GPIO_MAX) { |
| return -EINVAL; |
| } |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| |
| tmp = data->gpio_dircn & ~BIT(pin); |
| if (tmp != data->gpio_dircn) { |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_GPIO_DIRCN, tmp); |
| if (!err) { |
| data->gpio_dircn = tmp; |
| } |
| } |
| |
| k_mutex_unlock(&data->gpio_lock); |
| |
| return err; |
| } |
| |
| int lmp90xxx_gpio_set_pin_value(const struct device *dev, uint8_t pin, |
| bool value) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| int err = 0; |
| uint8_t tmp; |
| |
| if (pin > LMP90XXX_GPIO_MAX) { |
| return -EINVAL; |
| } |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| |
| tmp = data->gpio_dat; |
| WRITE_BIT(tmp, pin, value); |
| |
| if (tmp != data->gpio_dat) { |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_GPIO_DAT, tmp); |
| if (!err) { |
| data->gpio_dat = tmp; |
| } |
| } |
| |
| k_mutex_unlock(&data->gpio_lock); |
| |
| return err; |
| } |
| |
| int lmp90xxx_gpio_get_pin_value(const struct device *dev, uint8_t pin, |
| bool *value) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| int err = 0; |
| uint8_t tmp; |
| |
| if (pin > LMP90XXX_GPIO_MAX) { |
| return -EINVAL; |
| } |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| |
| err = lmp90xxx_read_reg8(dev, LMP90XXX_REG_GPIO_DAT, &tmp); |
| if (!err) { |
| *value = tmp & BIT(pin); |
| } |
| |
| k_mutex_unlock(&data->gpio_lock); |
| |
| return err; |
| } |
| |
| int lmp90xxx_gpio_port_get_raw(const struct device *dev, |
| gpio_port_value_t *value) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| uint8_t tmp; |
| int err; |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| err = lmp90xxx_read_reg8(dev, LMP90XXX_REG_GPIO_DAT, &tmp); |
| tmp &= ~(data->gpio_dircn); |
| k_mutex_unlock(&data->gpio_lock); |
| |
| *value = tmp; |
| |
| return err; |
| } |
| |
| int lmp90xxx_gpio_port_set_masked_raw(const struct device *dev, |
| gpio_port_pins_t mask, |
| gpio_port_value_t value) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| int err = 0; |
| uint8_t tmp; |
| |
| mask &= LMP90XXX_GPIO_DAT_MASK; |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| tmp = (data->gpio_dat & ~mask) | (value & mask); |
| if (tmp != data->gpio_dat) { |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_GPIO_DAT, tmp); |
| if (!err) { |
| data->gpio_dat = tmp; |
| } |
| } |
| k_mutex_unlock(&data->gpio_lock); |
| |
| return err; |
| } |
| |
| int lmp90xxx_gpio_port_set_bits_raw(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| int err = 0; |
| uint8_t tmp; |
| |
| tmp = pins & LMP90XXX_GPIO_DAT_MASK; |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| if (tmp != data->gpio_dat) { |
| tmp |= data->gpio_dat; |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_GPIO_DAT, tmp); |
| if (!err) { |
| data->gpio_dat = tmp; |
| } |
| } |
| k_mutex_unlock(&data->gpio_lock); |
| |
| return err; |
| } |
| |
| int lmp90xxx_gpio_port_clear_bits_raw(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| int err = 0; |
| uint8_t tmp; |
| |
| tmp = pins & LMP90XXX_GPIO_DAT_MASK; |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| if ((tmp & data->gpio_dat) != 0) { |
| tmp = data->gpio_dat & ~tmp; |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_GPIO_DAT, tmp); |
| if (!err) { |
| data->gpio_dat = tmp; |
| } |
| } |
| k_mutex_unlock(&data->gpio_lock); |
| |
| return err; |
| } |
| |
| int lmp90xxx_gpio_port_toggle_bits(const struct device *dev, |
| gpio_port_pins_t pins) |
| { |
| struct lmp90xxx_data *data = dev->data; |
| uint8_t tmp; |
| int err; |
| |
| tmp = pins & LMP90XXX_GPIO_DAT_MASK; |
| |
| k_mutex_lock(&data->gpio_lock, K_FOREVER); |
| tmp ^= data->gpio_dat; |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_GPIO_DAT, tmp); |
| if (!err) { |
| data->gpio_dat = tmp; |
| } |
| k_mutex_unlock(&data->gpio_lock); |
| |
| return err; |
| } |
| |
| #endif /* CONFIG_ADC_LMP90XXX_GPIO */ |
| |
| static int lmp90xxx_init(const struct device *dev) |
| { |
| const struct lmp90xxx_config *config = dev->config; |
| struct lmp90xxx_data *data = dev->data; |
| k_tid_t tid; |
| int err; |
| |
| data->dev = dev; |
| |
| k_mutex_init(&data->ura_lock); |
| k_sem_init(&data->acq_sem, 0, 1); |
| k_sem_init(&data->drdyb_sem, 0, 1); |
| #ifdef CONFIG_ADC_LMP90XXX_GPIO |
| k_mutex_init(&data->gpio_lock); |
| #endif /* CONFIG_ADC_LMP90XXX_GPIO */ |
| |
| /* Force INST1 + UAB on first access */ |
| data->ura = LMP90XXX_INVALID_URA; |
| |
| if (!spi_is_ready_dt(&config->bus)) { |
| LOG_ERR("SPI bus %s not ready", config->bus.bus->name); |
| return -ENODEV; |
| } |
| |
| err = lmp90xxx_soft_reset(dev); |
| if (err) { |
| LOG_ERR("failed to request soft reset (err %d)", err); |
| return err; |
| } |
| |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_SPI_HANDSHAKECN, |
| LMP90XXX_SDO_DRDYB_DRIVER(0x4)); |
| if (err) { |
| LOG_ERR("failed to set SPI handshake control (err %d)", |
| err); |
| return err; |
| } |
| |
| if (config->rtd_current) { |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_ADC_AUXCN, |
| LMP90XXX_RTD_CUR_SEL(config->rtd_current)); |
| if (err) { |
| LOG_ERR("failed to set RTD current (err %d)", err); |
| return err; |
| } |
| } |
| |
| if (IS_ENABLED(CONFIG_ADC_LMP90XXX_CRC)) { |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_SPI_CRC_CN, |
| LMP90XXX_EN_CRC(1) | |
| LMP90XXX_DRDYB_AFT_CRC(1)); |
| if (err) { |
| LOG_ERR("failed to enable CRC (err %d)", err); |
| return err; |
| } |
| } |
| |
| if (LMP90XXX_HAS_DRDYB(config)) { |
| err = gpio_pin_configure_dt(&config->drdyb, GPIO_INPUT); |
| if (err) { |
| LOG_ERR("failed to configure DRDYB GPIO pin (err %d)", |
| err); |
| return -EINVAL; |
| } |
| |
| gpio_init_callback(&data->drdyb_cb, lmp90xxx_drdyb_callback, |
| BIT(config->drdyb.pin)); |
| |
| err = gpio_add_callback(config->drdyb.port, &data->drdyb_cb); |
| if (err) { |
| LOG_ERR("failed to add DRDYB callback (err %d)", err); |
| return -EINVAL; |
| } |
| |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_SPI_DRDYBCN, |
| LMP90XXX_SPI_DRDYB_D6(1)); |
| if (err) { |
| LOG_ERR("failed to configure D6 as DRDYB (err %d)", |
| err); |
| return err; |
| } |
| |
| err = gpio_pin_interrupt_configure_dt(&config->drdyb, |
| GPIO_INT_EDGE_TO_ACTIVE); |
| if (err) { |
| LOG_ERR("failed to configure DRDBY interrupt (err %d)", |
| err); |
| return -EINVAL; |
| } |
| } |
| |
| tid = k_thread_create(&data->thread, data->stack, |
| K_KERNEL_STACK_SIZEOF(data->stack), |
| lmp90xxx_acquisition_thread, |
| data, NULL, NULL, |
| CONFIG_ADC_LMP90XXX_ACQUISITION_THREAD_PRIO, |
| 0, K_NO_WAIT); |
| k_thread_name_set(tid, "adc_lmp90xxx"); |
| |
| /* Put device in stand-by to prepare it for single-shot conversion */ |
| err = lmp90xxx_write_reg8(dev, LMP90XXX_REG_PWRCN, LMP90XXX_PWRCN(0x3)); |
| if (err) { |
| LOG_ERR("failed to request stand-by mode (err %d)", err); |
| return err; |
| } |
| |
| adc_context_unlock_unconditionally(&data->ctx); |
| |
| return 0; |
| } |
| |
| static const struct adc_driver_api lmp90xxx_adc_api = { |
| .channel_setup = lmp90xxx_adc_channel_setup, |
| .read = lmp90xxx_adc_read, |
| #ifdef CONFIG_ADC_ASYNC |
| .read_async = lmp90xxx_adc_read_async, |
| #endif |
| }; |
| |
| #define ASSERT_LMP90XXX_CURRENT_VALID(v) \ |
| BUILD_ASSERT(v == 0 || v == 100 || v == 200 || v == 300 || \ |
| v == 400 || v == 500 || v == 600 || v == 700 || \ |
| v == 800 || v == 900 || v == 1000, \ |
| "unsupported RTD current (" #v ")") |
| |
| #define LMP90XXX_UAMPS_TO_RTD_CUR_SEL(x) (x / 100) |
| |
| #define DT_INST_LMP90XXX(inst, t) DT_INST(inst, ti_lmp##t) |
| |
| #define LMP90XXX_INIT(t, n, res, ch) \ |
| ASSERT_LMP90XXX_CURRENT_VALID(UTIL_AND( \ |
| DT_NODE_HAS_PROP(DT_INST_LMP90XXX(n, t), rtd_current), \ |
| DT_PROP(DT_INST_LMP90XXX(n, t), rtd_current))); \ |
| static struct lmp90xxx_data lmp##t##_data_##n = { \ |
| ADC_CONTEXT_INIT_TIMER(lmp##t##_data_##n, ctx), \ |
| ADC_CONTEXT_INIT_LOCK(lmp##t##_data_##n, ctx), \ |
| ADC_CONTEXT_INIT_SYNC(lmp##t##_data_##n, ctx), \ |
| }; \ |
| static const struct lmp90xxx_config lmp##t##_config_##n = { \ |
| .bus = SPI_DT_SPEC_GET(DT_INST_LMP90XXX(n, t), SPI_OP_MODE_MASTER | \ |
| SPI_TRANSFER_MSB | SPI_WORD_SET(8), 0), \ |
| .drdyb = GPIO_DT_SPEC_GET_OR(DT_INST_LMP90XXX(n, t), drdyb_gpios, {0}), \ |
| .rtd_current = LMP90XXX_UAMPS_TO_RTD_CUR_SEL( \ |
| DT_PROP_OR(DT_INST_LMP90XXX(n, t), rtd_current, 0)), \ |
| .resolution = res, \ |
| .channels = ch, \ |
| }; \ |
| DEVICE_DT_DEFINE(DT_INST_LMP90XXX(n, t), \ |
| &lmp90xxx_init, NULL, \ |
| &lmp##t##_data_##n, \ |
| &lmp##t##_config_##n, POST_KERNEL, \ |
| CONFIG_ADC_INIT_PRIORITY, \ |
| &lmp90xxx_adc_api); |
| |
| /* |
| * LMP90077: 16 bit, 2 diff/4 se (4 channels), 0 currents |
| */ |
| #define LMP90077_INIT(n) LMP90XXX_INIT(90077, n, 16, 4) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_lmp90077 |
| DT_INST_FOREACH_STATUS_OKAY(LMP90077_INIT) |
| |
| /* |
| * LMP90078: 16 bit, 2 diff/4 se (4 channels), 2 currents |
| */ |
| #define LMP90078_INIT(n) LMP90XXX_INIT(90078, n, 16, 4) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_lmp90078 |
| DT_INST_FOREACH_STATUS_OKAY(LMP90078_INIT) |
| |
| /* |
| * LMP90079: 16 bit, 4 diff/7 se (7 channels), 0 currents, has VIN3-5 |
| */ |
| #define LMP90079_INIT(n) LMP90XXX_INIT(90079, n, 16, 7) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_lmp90079 |
| DT_INST_FOREACH_STATUS_OKAY(LMP90079_INIT) |
| |
| /* |
| * LMP90080: 16 bit, 4 diff/7 se (7 channels), 2 currents, has VIN3-5 |
| */ |
| #define LMP90080_INIT(n) LMP90XXX_INIT(90080, n, 16, 7) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_lmp90080 |
| DT_INST_FOREACH_STATUS_OKAY(LMP90080_INIT) |
| |
| /* |
| * LMP90097: 24 bit, 2 diff/4 se (4 channels), 0 currents |
| */ |
| #define LMP90097_INIT(n) LMP90XXX_INIT(90097, n, 24, 4) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_lmp90097 |
| DT_INST_FOREACH_STATUS_OKAY(LMP90097_INIT) |
| |
| /* |
| * LMP90098: 24 bit, 2 diff/4 se (4 channels), 2 currents |
| */ |
| #define LMP90098_INIT(n) LMP90XXX_INIT(90098, n, 24, 4) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_lmp90098 |
| DT_INST_FOREACH_STATUS_OKAY(LMP90098_INIT) |
| |
| /* |
| * LMP90099: 24 bit, 4 diff/7 se (7 channels), 0 currents, has VIN3-5 |
| */ |
| #define LMP90099_INIT(n) LMP90XXX_INIT(90099, n, 24, 7) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_lmp90099 |
| DT_INST_FOREACH_STATUS_OKAY(LMP90099_INIT) |
| |
| /* |
| * LMP90100: 24 bit, 4 diff/7 se (7 channels), 2 currents, has VIN3-5 |
| */ |
| #define LMP90100_INIT(n) LMP90XXX_INIT(90100, n, 24, 7) |
| #undef DT_DRV_COMPAT |
| #define DT_DRV_COMPAT ti_lmp90100 |
| DT_INST_FOREACH_STATUS_OKAY(LMP90100_INIT) |