You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
405 lines
15 KiB
405 lines
15 KiB
/* |
|
* Copyright (c) 2025 Texas Instruments Incorporated |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#define DT_DRV_COMPAT ti_am335x_adc |
|
|
|
#include <zephyr/device.h> |
|
#include <zephyr/kernel.h> |
|
#include <zephyr/drivers/adc.h> |
|
|
|
#define ADC_CONTEXT_USES_KERNEL_TIMER |
|
#include "adc_context.h" |
|
|
|
#include <zephyr/logging/log.h> |
|
#include <zephyr/irq.h> |
|
LOG_MODULE_REGISTER(ti_adc); |
|
|
|
#define TI_ADC_TOTAL_CHANNELS (8) |
|
#define TI_ADC_TOTAL_STEPS (16) |
|
#define TI_ADC_IDLE_TIMEOUT_MS (83) |
|
|
|
struct ti_adc_regs { |
|
uint8_t RESERVED_1[0x28]; /**< Reserved, offset: 0xA0 - 0x28 */ |
|
volatile uint32_t IRQ_STATUS; /**< Interrupt Status Register, offset: 0x28 */ |
|
volatile uint32_t IRQ_ENABLE; /**< Interrupt Enable Register, offset: 0x2C */ |
|
uint8_t RESERVED_2[0x10]; /**< Reserved, offset: 0x30 - 0x40 */ |
|
volatile uint32_t CONTROL; /**< Control Register, offset: 0x40 */ |
|
volatile uint32_t SEQ_STATUS; /**< Sequencer Status Register, offset: 0x44 */ |
|
uint8_t RESERVED_3[0xC]; /**< Reserved, offset: 0x48 - 0x54 */ |
|
volatile uint32_t STEPENABLE; /**< Sequencer Step Enable Register, offset: 0x54 */ |
|
uint8_t RESERVED_4[0xC]; /**< Reserved, offset: 0x58 - 0x64 */ |
|
volatile struct { |
|
volatile uint32_t CONFIG; /**< Step Config Register, offset: 0x64 + (j x 0x8) */ |
|
volatile uint32_t DELAY; /**< Step Delay Register, offset: 0x68 + (j x 0x8) */ |
|
} STEP[TI_ADC_TOTAL_STEPS]; |
|
volatile uint32_t FIFO0WC; /**< FIFO0 Word Count Register, offset: 0xE4 */ |
|
volatile uint32_t FIFO0THRSH; /**< FIFO0 Threshold Register, offset: 0xE8 */ |
|
uint8_t RESERVED_5[0x04]; /**< Reserved, offset: 0xEC - 0xF0 */ |
|
volatile uint32_t FIFO1WC; /**< FIFO1 Word Count Register, offset: 0xF0 */ |
|
volatile uint32_t FIFO1THRSH; /**< FIFO1 Threshold Register, offset: 0xE8 */ |
|
uint8_t RESERVED_6[0x08]; /**< Reserved, offset: 0xF8 - 0x100 */ |
|
volatile uint32_t FIFO0DATA; /**< FIFO0 Read Data Register, offset: 0x100 */ |
|
uint8_t RESERVED_7[0xFC]; /**< Reserved, offset: 0x104 - 0x200 */ |
|
volatile uint32_t FIFO1DATA; /**< FIFO1 Read Data Register, offset: 0x200 */ |
|
}; |
|
|
|
enum ti_adc_irq { |
|
TI_ADC_IRQ_END_OF_SEQUENCE_MISSING = BIT(0), /* End of Sequence Missing */ |
|
TI_ADC_IRQ_END_OF_SEQUENCE = BIT(1), /* End of Sequence */ |
|
TI_ADC_IRQ_FIFO0_THR = BIT(2), /* FIFO0 Threshold */ |
|
TI_ADC_IRQ_FIFO0_OVERFLOW = BIT(3), /* FIFO0 Overflow */ |
|
TI_ADC_IRQ_FIFO0_UNDERFLOW = BIT(4), /* FIFO0 Underflow */ |
|
TI_ADC_IRQ_FIFO1_THR = BIT(5), /* FIFO1 Threshold */ |
|
TI_ADC_IRQ_FIFO1_OVERFLOW = BIT(6), /* FIFO1 Overflow */ |
|
TI_ADC_IRQ_FIFO1_UNDERFLOW = BIT(7), /* FIFO1 Underflow */ |
|
TI_ADC_IRQ_OUT_OF_RANGE = BIT(8), /* Out Of Range*/ |
|
}; |
|
|
|
/* ADC Control Register */ |
|
#define TI_ADC_CONTROL_ENABLE BIT(0) /* Enable Sequencer */ |
|
#define TI_ADC_CONTROL_POWER_DOWN BIT(4) /* Power Off */ |
|
|
|
/* ADC Sequencer Status Register */ |
|
#define TI_ADC_SEQ_STATUS_FSM BIT(5) /* FSM Status */ |
|
#define TI_ADC_SEQ_STATUS_FSM_IDLE (0x0U) /* FSM Status - Idle */ |
|
#define TI_ADC_SEQ_STATUS_STEP GENMASK(4, 0) /* Current Step */ |
|
#define TI_ADC_SEQ_STATUS_STEP_IDLE (0x10U) /* Current Step - Idle */ |
|
|
|
/* ADC Sequencer Step Config Register */ |
|
#define TI_ADC_STEPCONFIG_MODE GENMASK(1, 0) /* Step Mode */ |
|
#define TI_ADC_STEPCONFIG_MODE_SINGLESHOT (0x0U) /* Step Mode - Continuous */ |
|
#define TI_ADC_STEPCONFIG_MODE_CONTINUOUS (0x1U) /* Step Mode - Singleshot */ |
|
#define TI_ADC_STEPCONFIG_AVERAGING GENMASK(4, 2) /* Step Averaging */ |
|
#define TI_ADC_STEPCONFIG_AVERAGING_MAX (4U) /* Step Averaging - Max */ |
|
#define TI_ADC_STEPCONFIG_SEL_INM GENMASK(18, 15) /* Negative Input */ |
|
#define TI_ADC_STEPCONFIG_SEL_INM_REFN (0x8U) /* Negative Input - Reference */ |
|
#define TI_ADC_STEPCONFIG_SEL_INP GENMASK(22, 19) /* Positive Input */ |
|
#define TI_ADC_STEPCONFIG_DIFFERENTIAL BIT(25) /* Step Differential */ |
|
#define TI_ADC_STEPCONFIG_FIFOSEL BIT(26) /* Selected FIFO */ |
|
|
|
/* ADC Sequencer Step Delay Register */ |
|
#define TI_ADC_STEPDELAY_OPENDELAY GENMASK(17, 0) /* Pre-Conversion Delay */ |
|
#define TI_ADC_STEPDELAY_OPENDELAY_MAX (0x3FFFFU) /* Pre-Conversion Delay - Max */ |
|
#define TI_ADC_STEPDELAY_SAMPLEDELAY GENMASK(31, 24) /* Conversion Delay */ |
|
#define TI_ADC_STEPDELAY_SAMPLEDELAY_MAX (0xFFU) /* Conversion Delay - Max */ |
|
|
|
/* FIFO Threshold Register */ |
|
#define TI_ADC_FIFO_THRESHOLD (40) |
|
|
|
/* FIFO Data Register */ |
|
#define TI_ADC_FIFODATA_ADCDATA GENMASK(11, 0) /* FIFO Data Mask */ |
|
|
|
#define DEV_CFG(dev) ((const struct ti_adc_cfg *)(dev)->config) |
|
#define DEV_DATA(dev) ((struct ti_adc_data *)(dev)->data) |
|
#define DEV_REGS(dev) ((struct ti_adc_regs *)DEVICE_MMIO_GET(dev)) |
|
|
|
struct ti_adc_cfg { |
|
DEVICE_MMIO_ROM; |
|
void (*irq_func)(const struct device *dev); |
|
uint32_t open_delay[TI_ADC_TOTAL_CHANNELS]; |
|
uint8_t oversampling[TI_ADC_TOTAL_CHANNELS]; |
|
uint8_t fifo; |
|
}; |
|
|
|
struct ti_adc_data { |
|
DEVICE_MMIO_RAM; |
|
struct adc_context ctx; |
|
const struct device *dev; |
|
uint8_t steps[TI_ADC_TOTAL_CHANNELS]; |
|
uint32_t fifo_irq_mask; |
|
volatile const uint32_t *fifo_wc_ptr; |
|
volatile const uint32_t *fifo_data_ptr; |
|
uint16_t *buffer; |
|
uint16_t *repeat_buffer; |
|
uint8_t chan_count; |
|
uint8_t step_count; |
|
}; |
|
|
|
/* Forward Declaration */ |
|
static int ti_adc_sequencer_start(const struct device *dev); |
|
|
|
static void adc_context_start_sampling(struct adc_context *ctx) |
|
{ |
|
struct ti_adc_data *data = CONTAINER_OF(ctx, struct ti_adc_data, ctx); |
|
struct ti_adc_regs *regs = DEV_REGS(data->dev); |
|
|
|
regs->CONTROL &= ~TI_ADC_CONTROL_ENABLE; |
|
|
|
/* enable steps */ |
|
for (int chan = 0; chan < TI_ADC_TOTAL_CHANNELS; chan++) { |
|
if (IS_BIT_SET(ctx->sequence.channels, chan)) { |
|
regs->STEPENABLE |= BIT(data->steps[chan] + 1); |
|
} |
|
} |
|
|
|
if (ti_adc_sequencer_start(data->dev) < 0) { |
|
LOG_ERR("Sequencer failed to start"); |
|
}; |
|
} |
|
|
|
static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat) |
|
{ |
|
struct ti_adc_data *data = CONTAINER_OF(ctx, struct ti_adc_data, ctx); |
|
|
|
if (repeat) { |
|
data->buffer = data->repeat_buffer; |
|
} else { |
|
data->buffer += data->chan_count; |
|
} |
|
} |
|
|
|
static int ti_adc_sequencer_start(const struct device *dev) |
|
{ |
|
struct ti_adc_data *data = DEV_DATA(dev); |
|
struct ti_adc_regs *regs = DEV_REGS(dev); |
|
uint64_t timeout = k_uptime_get(); |
|
uint32_t seq_status = regs->SEQ_STATUS; |
|
|
|
while (FIELD_GET(TI_ADC_SEQ_STATUS_FSM, seq_status) != TI_ADC_SEQ_STATUS_FSM_IDLE && |
|
FIELD_GET(TI_ADC_SEQ_STATUS_STEP, seq_status) != TI_ADC_SEQ_STATUS_STEP_IDLE) { |
|
/* Timeout */ |
|
if (k_uptime_get() - timeout > (TI_ADC_IDLE_TIMEOUT_MS * data->chan_count)) { |
|
return -ETIMEDOUT; |
|
} |
|
|
|
k_sleep(K_USEC(10)); /* sleep for 10us */ |
|
|
|
seq_status = regs->SEQ_STATUS; |
|
} |
|
|
|
regs->CONTROL |= TI_ADC_CONTROL_ENABLE; |
|
|
|
return 0; |
|
} |
|
|
|
static int ti_adc_channel_setup(const struct device *dev, const struct adc_channel_cfg *chan_cfg) |
|
{ |
|
const struct ti_adc_cfg *cfg = DEV_CFG(dev); |
|
struct ti_adc_data *data = DEV_DATA(dev); |
|
struct ti_adc_regs *regs = DEV_REGS(dev); |
|
const uint8_t chan = chan_cfg->channel_id; |
|
|
|
if (chan >= TI_ADC_TOTAL_CHANNELS) { |
|
LOG_ERR("Channel %d invalid, must be less than %d", chan, TI_ADC_TOTAL_CHANNELS); |
|
return -EINVAL; |
|
} |
|
|
|
if (chan_cfg->gain != ADC_GAIN_1) { |
|
LOG_ERR("Gain must be 1x"); |
|
return -EINVAL; |
|
} |
|
|
|
if (cfg->oversampling[chan] > TI_ADC_STEPCONFIG_AVERAGING_MAX) { |
|
LOG_ERR("Invalid oversampling value"); |
|
return -EINVAL; |
|
} |
|
|
|
if (cfg->open_delay[chan] > TI_ADC_STEPDELAY_OPENDELAY_MAX) { |
|
LOG_ERR("Invalid open delay"); |
|
return -EINVAL; |
|
} |
|
|
|
if (chan_cfg->acquisition_time > TI_ADC_STEPDELAY_SAMPLEDELAY_MAX) { |
|
LOG_ERR("Invalid acquisition time (sample delay)"); |
|
return -EINVAL; |
|
} |
|
|
|
#ifdef CONFIG_ADC_CONFIGURABLE_INPUTS |
|
if (!chan_cfg->differential && chan_cfg->input_negative != TI_ADC_STEPCONFIG_SEL_INM_REFN) { |
|
LOG_ERR("For single ended input, negative input must be REFN"); |
|
return -EINVAL; |
|
} |
|
#endif |
|
|
|
/* TODO: allow continuous mode when DMA is possible */ |
|
regs->STEP[data->step_count].CONFIG = |
|
FIELD_PREP(TI_ADC_STEPCONFIG_MODE, TI_ADC_STEPCONFIG_MODE_SINGLESHOT) | |
|
FIELD_PREP(TI_ADC_STEPCONFIG_AVERAGING, cfg->oversampling[chan]) | |
|
#ifdef CONFIG_ADC_CONFIGURABLE_INPUTS |
|
FIELD_PREP(TI_ADC_STEPCONFIG_SEL_INP, chan_cfg->input_positive) | |
|
FIELD_PREP(TI_ADC_STEPCONFIG_SEL_INM, chan_cfg->input_negative) | |
|
#else |
|
FIELD_PREP(TI_ADC_STEPCONFIG_SEL_INP, chan) | |
|
FIELD_PREP(TI_ADC_STEPCONFIG_SEL_INM, TI_ADC_STEPCONFIG_SEL_INM_REFN) | |
|
#endif |
|
FIELD_PREP(TI_ADC_STEPCONFIG_DIFFERENTIAL, chan_cfg->differential) | |
|
FIELD_PREP(TI_ADC_STEPCONFIG_FIFOSEL, cfg->fifo); |
|
|
|
regs->STEP[data->step_count].DELAY = |
|
FIELD_PREP(TI_ADC_STEPDELAY_OPENDELAY, cfg->open_delay[chan]) | |
|
FIELD_PREP(TI_ADC_STEPDELAY_SAMPLEDELAY, chan_cfg->acquisition_time); |
|
|
|
data->steps[chan] = data->step_count++; |
|
|
|
return 0; |
|
} |
|
|
|
static int ti_adc_read_start(const struct device *dev, const struct adc_sequence *sequence) |
|
{ |
|
struct ti_adc_data *data = DEV_DATA(dev); |
|
const uint8_t samplings = (sequence->options ? sequence->options->extra_samplings + 1 : 1); |
|
uint32_t required_size = 0; |
|
|
|
data->chan_count = 0; |
|
|
|
/* count channels enabled in sequence */ |
|
for (int chan = 0; chan < TI_ADC_TOTAL_CHANNELS; chan++) { |
|
if (IS_BIT_SET(sequence->channels, chan)) { |
|
data->chan_count++; |
|
} |
|
}; |
|
|
|
required_size = sizeof(uint16_t) * data->chan_count * samplings; |
|
|
|
if (sequence->buffer_size < required_size) { |
|
LOG_ERR("Buffer size is too small (%u/%u)", sequence->buffer_size, required_size); |
|
return -ENOMEM; |
|
} |
|
|
|
data->buffer = sequence->buffer; |
|
data->repeat_buffer = sequence->buffer; |
|
|
|
adc_context_start_read(&data->ctx, sequence); |
|
|
|
return adc_context_wait_for_completion(&data->ctx); |
|
} |
|
|
|
static int ti_adc_read_async(const struct device *dev, const struct adc_sequence *sequence, |
|
struct k_poll_signal *async) |
|
{ |
|
struct ti_adc_data *data = DEV_DATA(dev); |
|
int error; |
|
|
|
adc_context_lock(&data->ctx, async ? true : false, async); |
|
error = ti_adc_read_start(dev, sequence); |
|
adc_context_release(&data->ctx, error); |
|
|
|
return error; |
|
} |
|
|
|
static int ti_adc_read(const struct device *dev, const struct adc_sequence *sequence) |
|
{ |
|
return ti_adc_read_async(dev, sequence, NULL); |
|
} |
|
|
|
static int ti_adc_init(const struct device *dev) |
|
{ |
|
const struct ti_adc_cfg *cfg = DEV_CFG(dev); |
|
struct ti_adc_data *data = DEV_DATA(dev); |
|
struct ti_adc_regs *regs = DEV_REGS(dev); |
|
|
|
DEVICE_MMIO_MAP(dev, K_MEM_CACHE_NONE); |
|
|
|
cfg->irq_func(dev); |
|
|
|
if (cfg->fifo == 0) { |
|
/* FIFO 0 */ |
|
data->fifo_irq_mask = TI_ADC_IRQ_FIFO0_OVERFLOW | TI_ADC_IRQ_FIFO0_UNDERFLOW; |
|
data->fifo_wc_ptr = ®s->FIFO0WC; |
|
data->fifo_data_ptr = ®s->FIFO0DATA; |
|
regs->FIFO0THRSH = TI_ADC_FIFO_THRESHOLD; |
|
} else if (cfg->fifo == 1) { |
|
/* FIFO 1 */ |
|
data->fifo_irq_mask = TI_ADC_IRQ_FIFO1_OVERFLOW | TI_ADC_IRQ_FIFO1_UNDERFLOW; |
|
data->fifo_wc_ptr = ®s->FIFO1WC; |
|
data->fifo_data_ptr = ®s->FIFO1DATA; |
|
regs->FIFO1THRSH = TI_ADC_FIFO_THRESHOLD; |
|
} else { |
|
LOG_ERR("FIFO must be 0 or 1"); |
|
return -EINVAL; |
|
} |
|
|
|
/* clear interrupt status */ |
|
regs->IRQ_STATUS = data->fifo_irq_mask | TI_ADC_IRQ_END_OF_SEQUENCE; |
|
|
|
/* power up if not already up */ |
|
if (regs->CONTROL & TI_ADC_CONTROL_POWER_DOWN) { |
|
regs->CONTROL &= ~TI_ADC_CONTROL_POWER_DOWN; |
|
k_sleep(K_USEC(4)); /* wait at least 4us */ |
|
} |
|
|
|
/* enable interrupts */ |
|
regs->IRQ_ENABLE = data->fifo_irq_mask | TI_ADC_IRQ_END_OF_SEQUENCE; |
|
|
|
adc_context_unlock_unconditionally(&data->ctx); |
|
return 0; |
|
} |
|
|
|
static void ti_adc_isr(const struct device *dev) |
|
{ |
|
struct ti_adc_regs *regs = DEV_REGS(dev); |
|
struct ti_adc_data *data = DEV_DATA(dev); |
|
uint32_t status = regs->IRQ_STATUS; |
|
uint8_t count = 0; |
|
uint32_t fsm; |
|
|
|
/* fifo overflow or underflow */ |
|
if (status & data->fifo_irq_mask) { |
|
/* stop the sequencer */ |
|
regs->CONTROL &= ~TI_ADC_CONTROL_ENABLE; |
|
|
|
/* clear interrupt status */ |
|
regs->IRQ_STATUS |= data->fifo_irq_mask; |
|
|
|
/* wait for current conversion to finish */ |
|
do { |
|
fsm = FIELD_GET(TI_ADC_SEQ_STATUS_FSM, regs->SEQ_STATUS); |
|
} while (fsm != TI_ADC_SEQ_STATUS_FSM_IDLE && count++ < 100); |
|
|
|
/* start the sequencer */ |
|
regs->CONTROL |= TI_ADC_CONTROL_ENABLE; |
|
|
|
} else if (status & TI_ADC_IRQ_END_OF_SEQUENCE) { |
|
uint16_t wc = *data->fifo_wc_ptr; |
|
uint16_t i; |
|
|
|
for (i = 0; i < wc; i++) { |
|
/* write from fifo to buffer */ |
|
data->buffer[i] = FIELD_GET(TI_ADC_FIFODATA_ADCDATA, *data->fifo_data_ptr); |
|
} |
|
|
|
regs->IRQ_STATUS |= TI_ADC_IRQ_END_OF_SEQUENCE; |
|
|
|
adc_context_on_sampling_done(&data->ctx, dev); |
|
} |
|
} |
|
|
|
#define EXPLICIT_CHAN_PROP(node, prop) [DT_REG_ADDR(node)] = DT_PROP(node, prop) |
|
|
|
#define CHAN_PROP_LIST(n, prop) {DT_INST_FOREACH_CHILD_SEP_VARGS(n, EXPLICIT_CHAN_PROP, (,), prop)} |
|
|
|
#define TI_ADC_INIT(n) \ |
|
static DEVICE_API(adc, ti_adc_driver_api_##n) = { \ |
|
.channel_setup = ti_adc_channel_setup, \ |
|
.read = ti_adc_read, \ |
|
.ref_internal = DT_INST_PROP(n, ti_vrefp), \ |
|
IF_ENABLED(CONFIG_ADC_ASYNC, (.read_async = ti_adc_read_async,)) }; \ |
|
\ |
|
static void ti_adc_irq_setup_##n(const struct device *dev) \ |
|
{ \ |
|
IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), ti_adc_isr, \ |
|
DEVICE_DT_INST_GET(n), 0); \ |
|
irq_enable(DT_INST_IRQN(n)); \ |
|
} \ |
|
\ |
|
static const struct ti_adc_cfg ti_adc_cfg_##n = { \ |
|
DEVICE_MMIO_ROM_INIT(DT_DRV_INST(n)), \ |
|
.irq_func = &ti_adc_irq_setup_##n, \ |
|
.open_delay = CHAN_PROP_LIST(n, ti_open_delay), \ |
|
.oversampling = CHAN_PROP_LIST(n, zephyr_oversampling), \ |
|
.fifo = DT_INST_PROP(n, ti_fifo), \ |
|
}; \ |
|
\ |
|
static struct ti_adc_data ti_adc_data_##n = { \ |
|
ADC_CONTEXT_INIT_TIMER(ti_adc_data_##n, ctx), \ |
|
ADC_CONTEXT_INIT_LOCK(ti_adc_data_##n, ctx), \ |
|
ADC_CONTEXT_INIT_SYNC(ti_adc_data_##n, ctx), \ |
|
.dev = DEVICE_DT_INST_GET(n), \ |
|
}; \ |
|
\ |
|
DEVICE_DT_INST_DEFINE(n, &ti_adc_init, NULL, &ti_adc_data_##n, &ti_adc_cfg_##n, \ |
|
POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, &ti_adc_driver_api_##n); |
|
|
|
DT_INST_FOREACH_STATUS_OKAY(TI_ADC_INIT)
|
|
|