forked from zephyrproject-rtos/zephyr
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This adds a driver for Texas Instruments Cost-Optimized, Ultra-Small, 12-Bit, System-Monitoring ADCs. Currently only TLA2021 is supported, TLA2022 and TLA2024 may follow based on this driver. Signed-off-by: Caspar Friedrich <[email protected]>
- Loading branch information
1 parent
66018d5
commit 9291c9f
Showing
5 changed files
with
370 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Copyright (c) 2023 Caspar Friedrich <[email protected]> | ||
# 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,318 @@ | ||
/* | ||
* Copyright (c) 2023 Caspar Friedrich <[email protected]> | ||
* | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
#include <stdint.h> | ||
|
||
#include <zephyr/devicetree.h> | ||
#include <zephyr/drivers/adc.h> | ||
#include <zephyr/drivers/i2c.h> | ||
#include <zephyr/kernel.h> | ||
#include <zephyr/logging/log.h> | ||
#include <zephyr/sys/byteorder.h> | ||
|
||
#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); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Copyright (c) 2023 Caspar Friedrich <[email protected]> | ||
# 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 |