406 lines
12 KiB
C
406 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 const struct adc_driver_api 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)
|