| /* |
| * Copyright (c) 2024 Ambiq Micro Inc. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT ambiq_adc |
| |
| #include <zephyr/drivers/adc.h> |
| #include <zephyr/drivers/pinctrl.h> |
| #include <zephyr/pm/device.h> |
| #include <zephyr/pm/device_runtime.h> |
| #include <zephyr/kernel.h> |
| |
| #define ADC_CONTEXT_USES_KERNEL_TIMER |
| #include "adc_context.h" |
| |
| /* ambiq-sdk includes */ |
| #include <am_mcu_apollo.h> |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(adc_ambiq, CONFIG_ADC_LOG_LEVEL); |
| |
| typedef int (*ambiq_adc_pwr_func_t)(void); |
| #define PWRCTRL_MAX_WAIT_US 5 |
| /* Number of slots available. */ |
| #define AMBIQ_ADC_SLOT_NUMBER AM_HAL_ADC_MAX_SLOTS |
| |
| struct adc_ambiq_config { |
| uint32_t base; |
| int size; |
| uint8_t num_channels; |
| void (*irq_config_func)(void); |
| const struct pinctrl_dev_config *pin_cfg; |
| ambiq_adc_pwr_func_t pwr_func; |
| }; |
| |
| struct adc_ambiq_data { |
| struct adc_context ctx; |
| void *adcHandle; |
| uint16_t *buffer; |
| uint16_t *repeat_buffer; |
| uint8_t active_channels; |
| }; |
| |
| static int adc_ambiq_set_resolution(am_hal_adc_slot_prec_e *prec, uint8_t adc_resolution) |
| { |
| switch (adc_resolution) { |
| case 8: |
| *prec = AM_HAL_ADC_SLOT_8BIT; |
| break; |
| case 10: |
| *prec = AM_HAL_ADC_SLOT_10BIT; |
| break; |
| case 12: |
| *prec = AM_HAL_ADC_SLOT_12BIT; |
| break; |
| #if !defined(CONFIG_SOC_SERIES_APOLLO4X) |
| case 14: |
| *prec = AM_HAL_ADC_SLOT_14BIT; |
| break; |
| #endif |
| default: |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static int adc_ambiq_slot_config(const struct device *dev, const struct adc_sequence *sequence, |
| am_hal_adc_slot_chan_e channel, uint32_t ui32SlotNumber) |
| { |
| struct adc_ambiq_data *data = dev->data; |
| am_hal_adc_slot_config_t ADCSlotConfig; |
| |
| if (adc_ambiq_set_resolution(&ADCSlotConfig.ePrecisionMode, sequence->resolution) != 0) { |
| LOG_ERR("unsupported resolution %d", sequence->resolution); |
| return -ENOTSUP; |
| } |
| |
| /* Set up an ADC slot */ |
| ADCSlotConfig.eMeasToAvg = AM_HAL_ADC_SLOT_AVG_1; |
| ADCSlotConfig.eChannel = channel; |
| ADCSlotConfig.bWindowCompare = false; |
| ADCSlotConfig.bEnabled = true; |
| #if defined(CONFIG_SOC_SERIES_APOLLO4X) |
| ADCSlotConfig.ui32TrkCyc = AM_HAL_ADC_MIN_TRKCYC; |
| #endif |
| if (AM_HAL_STATUS_SUCCESS != |
| am_hal_adc_configure_slot(data->adcHandle, ui32SlotNumber, &ADCSlotConfig)) { |
| LOG_ERR("configuring ADC Slot 0 failed.\n"); |
| return -ENODEV; |
| } |
| |
| return 0; |
| } |
| |
| static void adc_ambiq_isr(const struct device *dev) |
| { |
| struct adc_ambiq_data *data = dev->data; |
| uint32_t ui32IntMask; |
| uint32_t ui32NumSamples; |
| am_hal_adc_sample_t Sample; |
| |
| /* Read the interrupt status. */ |
| am_hal_adc_interrupt_status(data->adcHandle, &ui32IntMask, true); |
| /* Clear the ADC interrupt.*/ |
| am_hal_adc_interrupt_clear(data->adcHandle, ui32IntMask); |
| |
| /* |
| * If we got a conversion completion interrupt (which should be our only |
| * ADC interrupt), go ahead and read the data. |
| */ |
| if (ui32IntMask & AM_HAL_ADC_INT_CNVCMP) { |
| for (uint32_t i = 0; i < data->active_channels; i++) { |
| /* Read the value from the FIFO. */ |
| ui32NumSamples = 1; |
| am_hal_adc_samples_read(data->adcHandle, false, NULL, &ui32NumSamples, |
| &Sample); |
| *data->buffer++ = Sample.ui32Sample; |
| } |
| am_hal_adc_disable(data->adcHandle); |
| adc_context_on_sampling_done(&data->ctx, dev); |
| } |
| } |
| |
| static int adc_ambiq_check_buffer_size(const struct adc_sequence *sequence, uint8_t active_channels) |
| { |
| size_t needed_buffer_size; |
| |
| needed_buffer_size = active_channels * sizeof(uint16_t); |
| |
| if (sequence->options) { |
| needed_buffer_size *= (1 + sequence->options->extra_samplings); |
| } |
| |
| if (sequence->buffer_size < needed_buffer_size) { |
| LOG_DBG("Provided buffer is too small (%u/%u)", sequence->buffer_size, |
| needed_buffer_size); |
| return -ENOMEM; |
| } |
| |
| return 0; |
| } |
| |
| static int adc_ambiq_start_read(const struct device *dev, const struct adc_sequence *sequence) |
| { |
| struct adc_ambiq_data *data = dev->data; |
| const struct adc_ambiq_config *cfg = dev->config; |
| uint8_t channel_id = 0; |
| uint32_t channels = 0; |
| uint8_t active_channels = 0; |
| uint8_t slot_index; |
| |
| int error = 0; |
| |
| if (sequence->channels & ~BIT_MASK(cfg->num_channels)) { |
| LOG_ERR("Incorrect channels, bitmask 0x%x", sequence->channels); |
| return -EINVAL; |
| } |
| |
| if (sequence->channels == 0UL) { |
| LOG_ERR("No channel selected"); |
| return -EINVAL; |
| } |
| |
| error = adc_ambiq_check_buffer_size(sequence, active_channels); |
| if (error < 0) { |
| return error; |
| } |
| |
| active_channels = POPCOUNT(sequence->channels); |
| if (active_channels > AMBIQ_ADC_SLOT_NUMBER) { |
| LOG_ERR("Too many channels for sequencer. Max: %d", AMBIQ_ADC_SLOT_NUMBER); |
| return -ENOTSUP; |
| } |
| |
| channels = sequence->channels; |
| for (slot_index = 0; slot_index < active_channels; slot_index++) { |
| channel_id = find_lsb_set(channels) - 1; |
| error = adc_ambiq_slot_config(dev, sequence, channel_id, slot_index); |
| if (error < 0) { |
| return error; |
| } |
| channels &= ~BIT(channel_id); |
| } |
| __ASSERT_NO_MSG(channels == 0); |
| |
| data->active_channels = active_channels; |
| data->buffer = sequence->buffer; |
| /* Start ADC conversion */ |
| adc_context_start_read(&data->ctx, sequence); |
| error = adc_context_wait_for_completion(&data->ctx); |
| |
| return error; |
| } |
| |
| static int adc_ambiq_read(const struct device *dev, const struct adc_sequence *sequence) |
| { |
| struct adc_ambiq_data *data = dev->data; |
| int error = 0; |
| |
| error = pm_device_runtime_get(dev); |
| if (error < 0) { |
| LOG_ERR("pm_device_runtime_get failed: %d", error); |
| } |
| |
| adc_context_lock(&data->ctx, false, NULL); |
| error = adc_ambiq_start_read(dev, sequence); |
| adc_context_release(&data->ctx, error); |
| |
| int ret = error; |
| |
| error = pm_device_runtime_put(dev); |
| if (error < 0) { |
| LOG_ERR("pm_device_runtime_put failed: %d", error); |
| } |
| |
| error = ret; |
| |
| return error; |
| } |
| |
| static int adc_ambiq_channel_setup(const struct device *dev, const struct adc_channel_cfg *chan_cfg) |
| { |
| const struct adc_ambiq_config *cfg = dev->config; |
| |
| if (chan_cfg->channel_id >= cfg->num_channels) { |
| LOG_ERR("unsupported channel id '%d'", chan_cfg->channel_id); |
| return -ENOTSUP; |
| } |
| |
| if (chan_cfg->gain != ADC_GAIN_1) { |
| LOG_ERR("Gain is not valid"); |
| return -ENOTSUP; |
| } |
| |
| if (chan_cfg->reference != ADC_REF_INTERNAL) { |
| LOG_ERR("Reference is not valid"); |
| return -ENOTSUP; |
| } |
| |
| if (chan_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { |
| LOG_ERR("unsupported acquisition_time '%d'", chan_cfg->acquisition_time); |
| return -ENOTSUP; |
| } |
| |
| if (chan_cfg->differential) { |
| LOG_ERR("Differential sampling not supported"); |
| return -ENOTSUP; |
| } |
| |
| return 0; |
| } |
| |
| static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repeat_sampling) |
| { |
| struct adc_ambiq_data *data = CONTAINER_OF(ctx, struct adc_ambiq_data, ctx); |
| |
| if (repeat_sampling) { |
| data->buffer = data->repeat_buffer; |
| } |
| } |
| |
| static void adc_context_start_sampling(struct adc_context *ctx) |
| { |
| struct adc_ambiq_data *data = CONTAINER_OF(ctx, struct adc_ambiq_data, ctx); |
| |
| data->repeat_buffer = data->buffer; |
| /* Enable the ADC. */ |
| am_hal_adc_enable(data->adcHandle); |
| /*Trigger the ADC*/ |
| am_hal_adc_sw_trigger(data->adcHandle); |
| } |
| |
| static int adc_ambiq_init(const struct device *dev) |
| { |
| struct adc_ambiq_data *data = dev->data; |
| const struct adc_ambiq_config *cfg = dev->config; |
| am_hal_adc_config_t ADCConfig; |
| |
| int ret; |
| |
| /* Initialize the ADC and get the handle*/ |
| if (AM_HAL_STATUS_SUCCESS != |
| am_hal_adc_initialize((cfg->base - ADC_BASE) / (cfg->size * 4), &data->adcHandle)) { |
| ret = -ENODEV; |
| LOG_ERR("Faile to initialize ADC, code:%d", ret); |
| return ret; |
| } |
| |
| /* power on ADC*/ |
| ret = cfg->pwr_func(); |
| |
| /* Set up the ADC configuration parameters. These settings are reasonable |
| * for accurate measurements at a low sample rate. |
| */ |
| #if !defined(CONFIG_SOC_SERIES_APOLLO4X) |
| ADCConfig.eClock = AM_HAL_ADC_CLKSEL_HFRC; |
| ADCConfig.eReference = AM_HAL_ADC_REFSEL_INT_1P5; |
| #else |
| ADCConfig.eClock = AM_HAL_ADC_CLKSEL_HFRC_24MHZ; |
| ADCConfig.eRepeatTrigger = AM_HAL_ADC_RPTTRIGSEL_TMR, |
| #endif |
| ADCConfig.ePolarity = AM_HAL_ADC_TRIGPOL_RISING; |
| ADCConfig.eTrigger = AM_HAL_ADC_TRIGSEL_SOFTWARE; |
| ADCConfig.eClockMode = AM_HAL_ADC_CLKMODE_LOW_POWER; |
| ADCConfig.ePowerMode = AM_HAL_ADC_LPMODE0; |
| ADCConfig.eRepeat = AM_HAL_ADC_SINGLE_SCAN; |
| if (AM_HAL_STATUS_SUCCESS != am_hal_adc_configure(data->adcHandle, &ADCConfig)) { |
| ret = -ENODEV; |
| LOG_ERR("Configuring ADC failed, code:%d", ret); |
| return ret; |
| } |
| |
| ret = pinctrl_apply_state(cfg->pin_cfg, PINCTRL_STATE_DEFAULT); |
| if (ret < 0) { |
| return ret; |
| } |
| |
| /* Enable the ADC interrupts in the ADC. */ |
| cfg->irq_config_func(); |
| am_hal_adc_interrupt_enable(data->adcHandle, AM_HAL_ADC_INT_CNVCMP); |
| |
| adc_context_unlock_unconditionally(&data->ctx); |
| |
| return 0; |
| } |
| |
| #ifdef CONFIG_ADC_ASYNC |
| static int adc_ambiq_read_async(const struct device *dev, const struct adc_sequence *sequence, |
| struct k_poll_signal *async) |
| { |
| struct adc_ambiq_data *data = dev->data; |
| int error = 0; |
| |
| error = pm_device_runtime_get(dev); |
| if (error < 0) { |
| LOG_ERR("pm_device_runtime_get failed: %d", error); |
| } |
| |
| adc_context_lock(&data->ctx, true, async); |
| error = adc_ambiq_start_read(dev, sequence); |
| adc_context_release(&data->ctx, error); |
| |
| int ret = error; |
| |
| error = pm_device_runtime_put(dev); |
| if (error < 0) { |
| LOG_ERR("pm_device_runtime_put failed: %d", error); |
| } |
| |
| error = ret; |
| |
| return error; |
| } |
| #endif |
| |
| #ifdef CONFIG_PM_DEVICE |
| static int adc_ambiq_pm_action(const struct device *dev, enum pm_device_action action) |
| { |
| struct adc_ambiq_data *data = dev->data; |
| uint32_t ret = 0; |
| am_hal_sysctrl_power_state_e status; |
| |
| switch (action) { |
| case PM_DEVICE_ACTION_RESUME: |
| status = AM_HAL_SYSCTRL_WAKE; |
| break; |
| case PM_DEVICE_ACTION_SUSPEND: |
| status = AM_HAL_SYSCTRL_DEEPSLEEP; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| |
| ret = am_hal_adc_power_control(data->adcHandle, status, true); |
| |
| if (ret != AM_HAL_STATUS_SUCCESS) { |
| return -EPERM; |
| } else { |
| return 0; |
| } |
| } |
| #endif /* CONFIG_PM_DEVICE */ |
| |
| #ifdef CONFIG_ADC_ASYNC |
| #define ADC_AMBIQ_DRIVER_API(n) \ |
| static const struct adc_driver_api adc_ambiq_driver_api_##n = { \ |
| .channel_setup = adc_ambiq_channel_setup, \ |
| .read = adc_ambiq_read, \ |
| .read_async = adc_ambiq_read_async, \ |
| .ref_internal = DT_INST_PROP(n, internal_vref_mv), \ |
| }; |
| #else |
| #define ADC_AMBIQ_DRIVER_API(n) \ |
| static const struct adc_driver_api adc_ambiq_driver_api_##n = { \ |
| .channel_setup = adc_ambiq_channel_setup, \ |
| .read = adc_ambiq_read, \ |
| .ref_internal = DT_INST_PROP(n, internal_vref_mv), \ |
| }; |
| #endif |
| |
| #define ADC_AMBIQ_INIT(n) \ |
| PINCTRL_DT_INST_DEFINE(n); \ |
| ADC_AMBIQ_DRIVER_API(n); \ |
| static int pwr_on_ambiq_adc_##n(void) \ |
| { \ |
| uint32_t addr = DT_REG_ADDR(DT_INST_PHANDLE(n, ambiq_pwrcfg)) + \ |
| DT_INST_PHA(n, ambiq_pwrcfg, offset); \ |
| sys_write32((sys_read32(addr) | DT_INST_PHA(n, ambiq_pwrcfg, mask)), addr); \ |
| k_busy_wait(PWRCTRL_MAX_WAIT_US); \ |
| return 0; \ |
| } \ |
| static void adc_irq_config_func_##n(void) \ |
| { \ |
| IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), adc_ambiq_isr, \ |
| DEVICE_DT_INST_GET(n), 0); \ |
| irq_enable(DT_INST_IRQN(n)); \ |
| }; \ |
| static struct adc_ambiq_data adc_ambiq_data_##n = { \ |
| ADC_CONTEXT_INIT_TIMER(adc_ambiq_data_##n, ctx), \ |
| ADC_CONTEXT_INIT_LOCK(adc_ambiq_data_##n, ctx), \ |
| ADC_CONTEXT_INIT_SYNC(adc_ambiq_data_##n, ctx), \ |
| }; \ |
| const static struct adc_ambiq_config adc_ambiq_config_##n = { \ |
| .base = DT_INST_REG_ADDR(n), \ |
| .size = DT_INST_REG_SIZE(n), \ |
| .num_channels = DT_PROP(DT_DRV_INST(n), channel_count), \ |
| .irq_config_func = adc_irq_config_func_##n, \ |
| .pin_cfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n), \ |
| .pwr_func = pwr_on_ambiq_adc_##n, \ |
| }; \ |
| PM_DEVICE_DT_INST_DEFINE(n, adc_ambiq_pm_action); \ |
| DEVICE_DT_INST_DEFINE(n, &adc_ambiq_init, PM_DEVICE_DT_INST_GET(n), &adc_ambiq_data_##n, \ |
| &adc_ambiq_config_##n, POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, \ |
| &adc_ambiq_driver_api_##n); |
| |
| DT_INST_FOREACH_STATUS_OKAY(ADC_AMBIQ_INIT) |