zephyr/drivers/adc/adc_numaker.c
Pieter De Gendt 8442b6a83f drivers: adc: Place API into iterable section
Move all adc driver api structs into an iterable section, this allows us
to verify if an api pointer is located in compatible linker section.

Signed-off-by: Pieter De Gendt <pieter.degendt@basalte.be>
2024-11-29 14:50:40 +01:00

405 lines
12 KiB
C

/*
* Copyright (c) 2023 Nuvoton Technology Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT nuvoton_numaker_adc
#include <zephyr/kernel.h>
#include <zephyr/drivers/reset.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/clock_control.h>
#include <zephyr/drivers/clock_control/clock_control_numaker.h>
#include <zephyr/logging/log.h>
#include <soc.h>
#include <NuMicro.h>
#define ADC_CONTEXT_USES_KERNEL_TIMER
#define ADC_CONTEXT_ENABLE_ON_COMPLETE
#include "adc_context.h"
LOG_MODULE_REGISTER(adc_numaker, CONFIG_ADC_LOG_LEVEL);
/* Device config */
struct adc_numaker_config {
/* eadc base address */
EADC_T *eadc_base;
uint8_t channel_cnt;
const struct reset_dt_spec reset;
/* clock configuration */
uint32_t clk_modidx;
uint32_t clk_src;
uint32_t clk_div;
const struct device *clk_dev;
const struct pinctrl_dev_config *pincfg;
void (*irq_config_func)(const struct device *dev);
};
/* Driver context/data */
struct adc_numaker_data {
struct adc_context ctx;
const struct device *dev;
uint16_t *buffer;
uint16_t *buf_end;
uint16_t *repeat_buffer;
bool is_differential;
uint32_t channels;
uint32_t acq_time;
};
static int adc_numaker_channel_setup(const struct device *dev,
const struct adc_channel_cfg *chan_cfg)
{
const struct adc_numaker_config *cfg = dev->config;
struct adc_numaker_data *data = dev->data;
if (chan_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) {
if ((ADC_ACQ_TIME_UNIT(chan_cfg->acquisition_time) != ADC_ACQ_TIME_TICKS) ||
(ADC_ACQ_TIME_VALUE(chan_cfg->acquisition_time) > 255)) {
LOG_ERR("Selected ADC acquisition time is not in 0~255 ticks");
return -EINVAL;
}
}
data->acq_time = ADC_ACQ_TIME_VALUE(chan_cfg->acquisition_time);
if (chan_cfg->gain != ADC_GAIN_1) {
LOG_ERR("Not support channel gain");
return -ENOTSUP;
}
if (chan_cfg->reference != ADC_REF_INTERNAL) {
LOG_ERR("Not support channel reference");
return -ENOTSUP;
}
if (chan_cfg->channel_id >= cfg->channel_cnt) {
LOG_ERR("Invalid channel (%u)", chan_cfg->channel_id);
return -EINVAL;
}
data->is_differential = (chan_cfg->differential) ? true : false;
return 0;
}
static int m_adc_numaker_validate_buffer_size(const struct device *dev,
const struct adc_sequence *sequence)
{
const struct adc_numaker_config *cfg = dev->config;
uint8_t channel_cnt = 0;
uint32_t mask;
size_t needed_size;
for (mask = BIT(cfg->channel_cnt - 1); mask != 0; mask >>= 1) {
if (mask & sequence->channels) {
channel_cnt++;
}
}
needed_size = channel_cnt * sizeof(uint16_t);
if (sequence->options) {
needed_size *= (1 + sequence->options->extra_samplings);
}
if (sequence->buffer_size < needed_size) {
return -ENOBUFS;
}
return 0;
}
static void adc_numaker_isr(const struct device *dev)
{
const struct adc_numaker_config *cfg = dev->config;
EADC_T *eadc = cfg->eadc_base;
struct adc_numaker_data *const data = dev->data;
uint32_t channel_mask = data->channels;
uint32_t module_mask = channel_mask;
uint32_t module_id;
uint16_t conv_data;
uint32_t pend_flag;
LOG_DBG("ADC ISR pend flag: 0x%X\n", pend_flag);
LOG_DBG("ADC ISR STATUS2[0x%x] STATUS3[0x%x]", eadc->STATUS2, eadc->STATUS3);
/* Complete the conversion of channels.
* Check EAC idle by EADC_STATUS2_BUSY_Msk
* Check trigger source coming by EADC_STATUS2_ADOVIF_Msk
* Confirm all sample modules are idle by EADC_STATUS2_ADOVIF_Msk
*/
if (!(eadc->STATUS2 & EADC_STATUS2_BUSY_Msk) &&
((eadc->STATUS3 & EADC_STATUS3_CURSPL_Msk) == EADC_STATUS3_CURSPL_Msk)) {
/* Stop the conversion for sample module */
EADC_STOP_CONV(eadc, module_mask);
/* Disable sample module A/D ADINT0 interrupt. */
EADC_DISABLE_INT(eadc, BIT0);
/* Disable the sample module ADINT0 interrupt source */
EADC_DISABLE_SAMPLE_MODULE_INT(eadc, 0, module_mask);
/* Get conversion data of each sample module for selected channel */
while (module_mask) {
module_id = find_lsb_set(module_mask) - 1;
conv_data = EADC_GET_CONV_DATA(eadc, module_id);
if (data->buffer < data->buf_end) {
*data->buffer++ = conv_data;
LOG_DBG("ADC ISR id=%d, data=0x%x", module_id, conv_data);
}
module_mask &= ~BIT(module_id);
/* Disable all channels on each sample module */
eadc->SCTL[module_id] = 0;
}
/* Inform sampling is done */
adc_context_on_sampling_done(&data->ctx, data->dev);
}
/* Clear the A/D ADINT0 interrupt flag */
EADC_CLR_INT_FLAG(eadc, EADC_STATUS2_ADIF0_Msk);
}
static void m_adc_numaker_start_scan(const struct device *dev)
{
const struct adc_numaker_config *cfg = dev->config;
EADC_T *eadc = cfg->eadc_base;
struct adc_numaker_data *const data = dev->data;
uint32_t channel_mask = data->channels;
uint32_t module_mask = channel_mask;
uint32_t channel_id;
uint32_t module_id;
/* Configure the sample module, analog input channel and software trigger source */
while (channel_mask) {
channel_id = find_lsb_set(channel_mask) - 1;
module_id = channel_id;
channel_mask &= ~BIT(channel_id);
EADC_ConfigSampleModule(eadc, module_id,
EADC_SOFTWARE_TRIGGER, channel_id);
/* Set sample module external sampling time to 0 */
EADC_SetExtendSampleTime(eadc, module_id, data->acq_time);
}
/* Clear the A/D ADINT0 interrupt flag for safe */
EADC_CLR_INT_FLAG(eadc, EADC_STATUS2_ADIF0_Msk);
/* Enable sample module A/D ADINT0 interrupt. */
EADC_ENABLE_INT(eadc, BIT0);
/* Enable sample module interrupt ADINT0. */
EADC_ENABLE_SAMPLE_MODULE_INT(eadc, 0, module_mask);
/* Start conversion */
EADC_START_CONV(eadc, module_mask);
}
/* Implement ADC API functions of adc_context.h
* - adc_context_start_sampling()
* - adc_context_update_buffer_pointer()
*/
static void adc_context_start_sampling(struct adc_context *ctx)
{
struct adc_numaker_data *const data =
CONTAINER_OF(ctx, struct adc_numaker_data, ctx);
data->repeat_buffer = data->buffer;
data->channels = ctx->sequence.channels;
/* Start ADC conversion for sample modules/channels */
m_adc_numaker_start_scan(data->dev);
}
static void adc_context_update_buffer_pointer(struct adc_context *ctx,
bool repeat_sampling)
{
struct adc_numaker_data *data =
CONTAINER_OF(ctx, struct adc_numaker_data, ctx);
if (repeat_sampling) {
data->buffer = data->repeat_buffer;
}
}
static void adc_context_on_complete(struct adc_context *ctx, int status)
{
struct adc_numaker_data *data =
CONTAINER_OF(ctx, struct adc_numaker_data, ctx);
const struct adc_numaker_config *cfg = data->dev->config;
EADC_T *eadc = cfg->eadc_base;
ARG_UNUSED(status);
/* Disable ADC */
EADC_Close(eadc);
}
static int m_adc_numaker_start_read(const struct device *dev,
const struct adc_sequence *sequence)
{
const struct adc_numaker_config *cfg = dev->config;
struct adc_numaker_data *data = dev->data;
EADC_T *eadc = cfg->eadc_base;
int err;
err = m_adc_numaker_validate_buffer_size(dev, sequence);
if (err) {
LOG_ERR("ADC provided buffer is too small");
return err;
}
if (!sequence->resolution) {
LOG_ERR("ADC resolution is not valid");
return -EINVAL;
}
LOG_DBG("Configure resolution=%d", sequence->resolution);
/* Enable the A/D converter */
if (data->is_differential) {
EADC_Open(eadc, EADC_CTL_DIFFEN_DIFFERENTIAL);
} else {
EADC_Open(eadc, EADC_CTL_DIFFEN_SINGLE_END);
}
data->buffer = sequence->buffer;
data->buf_end = data->buffer + sequence->buffer_size / sizeof(uint16_t);
/* Start ADC conversion */
adc_context_start_read(&data->ctx, sequence);
return adc_context_wait_for_completion(&data->ctx);
}
static int adc_numaker_read(const struct device *dev,
const struct adc_sequence *sequence)
{
struct adc_numaker_data *data = dev->data;
int err;
adc_context_lock(&data->ctx, false, NULL);
err = m_adc_numaker_start_read(dev, sequence);
adc_context_release(&data->ctx, err);
return err;
}
#ifdef CONFIG_ADC_ASYNC
static int adc_numaker_read_async(const struct device *dev,
const struct adc_sequence *sequence,
struct k_poll_signal *async)
{
struct adc_numaker_data *data = dev->data;
int err;
adc_context_lock(&data->ctx, true, async);
err = m_adc_numaker_start_read(dev, sequence);
adc_context_release(&data->ctx, err);
return err;
}
#endif
static DEVICE_API(adc, adc_numaker_driver_api) = {
.channel_setup = adc_numaker_channel_setup,
.read = adc_numaker_read,
#ifdef CONFIG_ADC_ASYNC
.read_async = adc_numaker_read_async,
#endif
};
static int adc_numaker_init(const struct device *dev)
{
const struct adc_numaker_config *cfg = dev->config;
struct adc_numaker_data *data = dev->data;
int err;
struct numaker_scc_subsys scc_subsys;
/* Validate this module's reset object */
if (!device_is_ready(cfg->reset.dev)) {
LOG_ERR("reset controller not ready");
return -ENODEV;
}
data->dev = dev;
SYS_UnlockReg();
/* CLK controller */
memset(&scc_subsys, 0x00, sizeof(scc_subsys));
scc_subsys.subsys_id = NUMAKER_SCC_SUBSYS_ID_PCC;
scc_subsys.pcc.clk_modidx = cfg->clk_modidx;
scc_subsys.pcc.clk_src = cfg->clk_src;
scc_subsys.pcc.clk_div = cfg->clk_div;
/* Equivalent to CLK_EnableModuleClock() */
err = clock_control_on(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys);
if (err != 0) {
goto done;
}
/* Equivalent to CLK_SetModuleClock() */
err = clock_control_configure(cfg->clk_dev, (clock_control_subsys_t)&scc_subsys, NULL);
if (err != 0) {
goto done;
}
err = pinctrl_apply_state(cfg->pincfg, PINCTRL_STATE_DEFAULT);
if (err) {
LOG_ERR("Failed to apply pinctrl state");
goto done;
}
/* Reset EADC to default state, same as BSP's SYS_ResetModule(id_rst) */
reset_line_toggle_dt(&cfg->reset);
/* Enable NVIC */
cfg->irq_config_func(dev);
/* Init mutex of adc_context */
adc_context_unlock_unconditionally(&data->ctx);
done:
SYS_LockReg();
return err;
}
#define ADC_NUMAKER_IRQ_CONFIG_FUNC(n) \
static void adc_numaker_irq_config_func_##n(const struct device *dev) \
{ \
IRQ_CONNECT(DT_INST_IRQN(n), \
DT_INST_IRQ(n, priority), \
adc_numaker_isr, \
DEVICE_DT_INST_GET(n), 0); \
\
irq_enable(DT_INST_IRQN(n)); \
}
#define ADC_NUMAKER_INIT(inst) \
PINCTRL_DT_INST_DEFINE(inst); \
ADC_NUMAKER_IRQ_CONFIG_FUNC(inst) \
\
static const struct adc_numaker_config adc_numaker_cfg_##inst = { \
.eadc_base = (EADC_T *)DT_INST_REG_ADDR(inst), \
.channel_cnt = DT_INST_PROP(inst, channels), \
.reset = RESET_DT_SPEC_INST_GET(inst), \
.clk_modidx = DT_INST_CLOCKS_CELL(inst, clock_module_index), \
.clk_src = DT_INST_CLOCKS_CELL(inst, clock_source), \
.clk_div = DT_INST_CLOCKS_CELL(inst, clock_divider), \
.clk_dev = DEVICE_DT_GET(DT_PARENT(DT_INST_CLOCKS_CTLR(inst))), \
.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(inst), \
.irq_config_func = adc_numaker_irq_config_func_##inst, \
}; \
\
static struct adc_numaker_data adc_numaker_data_##inst = { \
ADC_CONTEXT_INIT_TIMER(adc_numaker_data_##inst, ctx), \
ADC_CONTEXT_INIT_LOCK(adc_numaker_data_##inst, ctx), \
ADC_CONTEXT_INIT_SYNC(adc_numaker_data_##inst, ctx), \
}; \
DEVICE_DT_INST_DEFINE(inst, \
&adc_numaker_init, NULL, \
&adc_numaker_data_##inst, &adc_numaker_cfg_##inst, \
POST_KERNEL, CONFIG_ADC_INIT_PRIORITY, \
&adc_numaker_driver_api);
DT_INST_FOREACH_STATUS_OKAY(ADC_NUMAKER_INIT)