diff --git a/drivers/adc/CMakeLists.txt b/drivers/adc/CMakeLists.txt index 63be06b419a..eded385c80c 100644 --- a/drivers/adc/CMakeLists.txt +++ b/drivers/adc/CMakeLists.txt @@ -41,3 +41,4 @@ zephyr_library_sources_ifdef(CONFIG_ADC_GECKO_IADC iadc_gecko.c) zephyr_library_sources_ifdef(CONFIG_ADC_INFINEON_CAT1 adc_ifx_cat1.c) zephyr_library_sources_ifdef(CONFIG_ADC_SMARTBOND_GPADC adc_smartbond_gpadc.c) zephyr_library_sources_ifdef(CONFIG_ADC_SMARTBOND_SDADC adc_smartbond_sdadc.c) +zephyr_library_sources_ifdef(CONFIG_ADC_TLA2021 adc_tla2021.c) diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index 0cfcbf05ecb..48c57c71a35 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -108,4 +108,6 @@ source "drivers/adc/Kconfig.ifx_cat1" source "drivers/adc/Kconfig.smartbond" +source "drivers/adc/Kconfig.tla2021" + endif # ADC diff --git a/drivers/adc/Kconfig.tla2021 b/drivers/adc/Kconfig.tla2021 new file mode 100644 index 00000000000..fff48ed82af --- /dev/null +++ b/drivers/adc/Kconfig.tla2021 @@ -0,0 +1,34 @@ +# Copyright (c) 2023 Caspar Friedrich +# SPDX-License-Identifier: Apache-2.0 + +config ADC_TLA2021 + bool "Texas Instruments TLA2021 Low-Power ADC" + default y + depends on DT_HAS_TI_TLA2021_ENABLED + select I2C + help + TLA202x Cost-Optimized, Ultra-Small, 12-Bit, System-Monitoring ADCs + +if ADC_TLA2021 + +config ADC_TLA2021_INIT_PRIORITY + int "Priority for the driver initialization" + default ADC_INIT_PRIORITY + help + Fine tune the priority for the driver initialization. Make sure it's + higher (-> lower priority) than I2C_INIT_PRIORITY. + +config ADC_TLA2021_ACQUISITION_THREAD_PRIORITY + int "Priority for the data acquisition thread" + default 0 + help + Execution priority for the internal data acquisition thread. + +config ADC_TLA2021_ACQUISITION_THREAD_STACK_SIZE + int "Stack size for the data acquisition thread" + default 512 + help + Stack size for the internal data acquisition thread. Requires room + for I2C operations. + +endif # ADC_TLA2021 diff --git a/drivers/adc/adc_tla2021.c b/drivers/adc/adc_tla2021.c new file mode 100644 index 00000000000..8c68c8b81ee --- /dev/null +++ b/drivers/adc/adc_tla2021.c @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2023 Caspar Friedrich + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define ADC_CONTEXT_USES_KERNEL_TIMER + +/* + * This requires to be included _after_ `#define ADC_CONTEXT_USES_KERNEL_TIMER` + */ +#include "adc_context.h" + +#define DT_DRV_COMPAT ti_tla2021 + +LOG_MODULE_REGISTER(tla2021, CONFIG_ADC_LOG_LEVEL); + +#define ACQ_THREAD_PRIORITY CONFIG_ADC_TLA2021_ACQUISITION_THREAD_PRIORITY +#define ACQ_THREAD_STACK_SIZE CONFIG_ADC_TLA2021_ACQUISITION_THREAD_STACK_SIZE + +#define ADC_CHANNEL_msk BIT(0) +#define ADC_RESOLUTION 12 + +/* + * Conversion Data Register (RP = 00h) [reset = 0000h] + */ +#define REG_DATA 0x00 +#define REG_DATA_pos 4 + +/* + * Configuration Register (RP = 01h) [reset = 8583h] + */ +#define REG_CONFIG 0x01 +#define REG_CONFIG_DEFAULT 0x8583 +#define REG_CONFIG_DR_pos 5 +#define REG_CONFIG_MODE_pos 8 +#define REG_CONFIG_PGA_pos 9 /* TLA2022 and TLA2024 Only */ +#define REG_CONFIG_MUX_pos 12 /* TLA2024 Only */ +#define REG_CONFIG_OS_pos 15 +#define REG_CONFIG_OS_msk (BIT_MASK(1) << REG_CONFIG_OS_pos) + +typedef int16_t tla2021_reg_data_t; +typedef uint16_t tla2021_reg_config_t; + +struct tla2021_config { + const struct i2c_dt_spec bus; + k_tid_t acq_thread_id; +}; + +struct tla2021_data { + const struct device *dev; + struct adc_context ctx; + struct k_sem acq_lock; + tla2021_reg_data_t *buffer; + tla2021_reg_data_t *repeat_buffer; + + /* + * Shadow register + */ + tla2021_reg_config_t reg_config; +}; + +static int tla2021_read_register(const struct device *dev, uint8_t reg, uint16_t *value) +{ + int ret; + + const struct tla2021_config *config = dev->config; + uint8_t tmp[2]; + + ret = i2c_write_read_dt(&config->bus, ®, sizeof(reg), tmp, sizeof(tmp)); + if (ret) { + return ret; + } + + *value = sys_get_be16(tmp); + + return 0; +} + +static int tla2021_write_register(const struct device *dev, uint8_t reg, uint16_t value) +{ + int ret; + + const struct tla2021_config *config = dev->config; + uint8_t tmp[3] = {reg}; + + sys_put_be16(value, &tmp[1]); + + ret = i2c_write_dt(&config->bus, tmp, sizeof(tmp)); + if (ret) { + return ret; + } + + return 0; +} + +static int tla2021_channel_setup(const struct device *dev, const struct adc_channel_cfg *cfg) +{ + if (cfg->gain != ADC_GAIN_1) { + LOG_ERR("Invalid gain"); + return -EINVAL; + } + + if (cfg->reference != ADC_REF_INTERNAL) { + LOG_ERR("Invalid reference"); + return -EINVAL; + } + + if (cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { + LOG_ERR("Invalid acquisition time"); + return -EINVAL; + } + + return 0; +} + +static int tla2021_start_read(const struct device *dev, const struct adc_sequence *seq) +{ + struct tla2021_data *data = dev->data; + + const size_t num_extra_samples = seq->options ? seq->options->extra_samplings : 0; + const size_t num_samples = (1 + num_extra_samples) * POPCOUNT(seq->channels); + + if (!(seq->channels & ADC_CHANNEL_msk)) { + LOG_ERR("Selected channel(s) not supported: %x", seq->channels); + return -EINVAL; + } + + if (seq->resolution != ADC_RESOLUTION) { + LOG_ERR("Selected resolution not supported: %d", seq->resolution); + return -EINVAL; + } + + if (seq->oversampling) { + LOG_ERR("Oversampling is not supported"); + return -EINVAL; + } + + if (seq->calibrate) { + LOG_ERR("Calibration is not supported"); + return -EINVAL; + } + + if (!seq->buffer) { + LOG_ERR("Buffer invalid"); + return -EINVAL; + } + + if (seq->buffer_size < (num_samples * sizeof(tla2021_reg_data_t))) { + LOG_ERR("buffer size too small"); + return -EINVAL; + } + + data->buffer = seq->buffer; + + adc_context_start_read(&data->ctx, seq); + + return adc_context_wait_for_completion(&data->ctx); +} + +static int tla2021_read_async(const struct device *dev, const struct adc_sequence *seq, + struct k_poll_signal *async) +{ + int ret; + + struct tla2021_data *data = dev->data; + + adc_context_lock(&data->ctx, async ? true : false, async); + ret = tla2021_start_read(dev, seq); + adc_context_release(&data->ctx, ret); + + return ret; +} + +static int tla2021_read(const struct device *dev, const struct adc_sequence *seq) +{ + return tla2021_read_async(dev, seq, NULL); +} + +static void adc_context_start_sampling(struct adc_context *ctx) +{ + int ret; + + struct tla2021_data *data = CONTAINER_OF(ctx, struct tla2021_data, ctx); + const struct device *dev = data->dev; + + tla2021_reg_config_t reg = data->reg_config; + + /* + * Start single-shot conversion + */ + WRITE_BIT(reg, REG_CONFIG_MODE_pos, 1); + WRITE_BIT(reg, REG_CONFIG_OS_pos, 1); + ret = tla2021_write_register(dev, REG_CONFIG, reg); + if (ret) { + LOG_WRN("Failed to start conversion"); + } + + data->repeat_buffer = data->buffer; + + k_sem_give(&data->acq_lock); +} + +static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat_sampling) +{ + struct tla2021_data *data = CONTAINER_OF(ctx, struct tla2021_data, ctx); + + if (repeat_sampling) { + data->buffer = data->repeat_buffer; + } +} + +static void tla2021_acq_thread_fn(void *p1, void *p2, void *p3) +{ + int ret; + + struct tla2021_data *data = p1; + const struct device *dev = data->dev; + + while (true) { + k_sem_take(&data->acq_lock, K_FOREVER); + + tla2021_reg_config_t reg; + tla2021_reg_data_t res; + + /* + * Wait until sampling is done + */ + do { + ret = tla2021_read_register(dev, REG_CONFIG, ®); + if (ret < 0) { + adc_context_complete(&data->ctx, ret); + } + } while (!(reg & REG_CONFIG_OS_msk)); + + /* + * Read result + */ + ret = tla2021_read_register(dev, REG_DATA, &res); + if (ret) { + adc_context_complete(&data->ctx, ret); + } + + /* + * ADC data is stored in the upper 12 bits + */ + res >>= REG_DATA_pos; + *data->buffer++ = res; + + adc_context_on_sampling_done(&data->ctx, data->dev); + } +} + +static int tla2021_init(const struct device *dev) +{ + int ret; + + const struct tla2021_config *config = dev->config; + struct tla2021_data *data = dev->data; + + k_sem_init(&data->acq_lock, 0, 1); + + if (!i2c_is_ready_dt(&config->bus)) { + LOG_ERR("Bus not ready"); + return -EINVAL; + } + + ret = tla2021_write_register(dev, REG_CONFIG, data->reg_config); + if (ret) { + LOG_ERR("Device reset failed: %d", ret); + return ret; + } + + adc_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static const struct adc_driver_api tla2021_driver_api = { + .channel_setup = tla2021_channel_setup, + .read = tla2021_read, + .ref_internal = 2048, +#ifdef CONFIG_ADC_ASYNC + .read_async = tla2021_read_async, +#endif +}; + +#define TLA2021_INIT(n) \ + static const struct tla2021_config inst_##n##_config; \ + static struct tla2021_data inst_##n##_data; \ + K_THREAD_DEFINE(inst_##n##_thread, ACQ_THREAD_STACK_SIZE, tla2021_acq_thread_fn, \ + &inst_##n##_data, NULL, NULL, ACQ_THREAD_PRIORITY, 0, 0); \ + static const struct tla2021_config inst_##n##_config = { \ + .bus = I2C_DT_SPEC_INST_GET(n), \ + .acq_thread_id = inst_##n##_thread, \ + }; \ + static struct tla2021_data inst_##n##_data = { \ + .dev = DEVICE_DT_INST_GET(n), \ + ADC_CONTEXT_INIT_LOCK(inst_##n##_data, ctx), \ + ADC_CONTEXT_INIT_TIMER(inst_##n##_data, ctx), \ + ADC_CONTEXT_INIT_SYNC(inst_##n##_data, ctx), \ + .reg_config = REG_CONFIG_DEFAULT, \ + }; \ + DEVICE_DT_INST_DEFINE(n, &tla2021_init, NULL, &inst_##n##_data, &inst_##n##_config, \ + POST_KERNEL, CONFIG_ADC_TLA2021_INIT_PRIORITY, &tla2021_driver_api); + +DT_INST_FOREACH_STATUS_OKAY(TLA2021_INIT) + +BUILD_ASSERT(CONFIG_I2C_INIT_PRIORITY < CONFIG_ADC_TLA2021_INIT_PRIORITY); diff --git a/dts/bindings/adc/ti,tla2021.yaml b/dts/bindings/adc/ti,tla2021.yaml new file mode 100644 index 00000000000..4b3ad6b6379 --- /dev/null +++ b/dts/bindings/adc/ti,tla2021.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2023 Caspar Friedrich +# SPDX-License-Identifier: Apache-2.0 + +description: Texas Instruments TLA2021 Low-Power ADC + +compatible: "ti,tla2021" + +include: [i2c-device.yaml, adc-controller.yaml] + +properties: + "#io-channel-cells": + const: 1 + +io-channel-cells: + - input