diff --git a/drivers/adc/CMakeLists.txt b/drivers/adc/CMakeLists.txt index d5ad16ca2f..b41100e89f 100644 --- a/drivers/adc/CMakeLists.txt +++ b/drivers/adc/CMakeLists.txt @@ -10,6 +10,7 @@ zephyr_library_sources_ifdef(CONFIG_ADC_MCUX_ADC12 adc_mcux_adc12.c) zephyr_library_sources_ifdef(CONFIG_ADC_MCUX_ADC16 adc_mcux_adc16.c) zephyr_library_sources_ifdef(CONFIG_ADC_MCUX_12B1MSPS_SAR adc_mcux_12b1msps_sar.c) zephyr_library_sources_ifdef(CONFIG_ADC_MCUX_LPADC adc_mcux_lpadc.c) +zephyr_library_sources_ifdef(CONFIG_ADC_VF610 adc_vf610.c) zephyr_library_sources_ifdef(CONFIG_ADC_SAM_AFEC adc_sam_afec.c) zephyr_library_sources_ifdef(CONFIG_ADC_NRFX_ADC adc_nrfx_adc.c) zephyr_library_sources_ifdef(CONFIG_ADC_NRFX_SAADC adc_nrfx_saadc.c) diff --git a/drivers/adc/Kconfig b/drivers/adc/Kconfig index bd60946556..b0531ccb2f 100644 --- a/drivers/adc/Kconfig +++ b/drivers/adc/Kconfig @@ -76,6 +76,8 @@ source "drivers/adc/Kconfig.cc13xx_cc26xx" source "drivers/adc/Kconfig.adc_emul" +source "drivers/adc/Kconfig.vf610" + source "drivers/adc/Kconfig.test" source "drivers/adc/Kconfig.ads1x1x" diff --git a/drivers/adc/Kconfig.vf610 b/drivers/adc/Kconfig.vf610 new file mode 100644 index 0000000000..88a8dc0c9d --- /dev/null +++ b/drivers/adc/Kconfig.vf610 @@ -0,0 +1,11 @@ +# ADC configuration options + +# Copyright (c) 2021 Antonio Tessarolo +# SPDX-License-Identifier: Apache-2.0 + +config ADC_VF610 + bool "VF610 ADC driver" + depends on DT_HAS_NXP_VF610_ADC_ENABLED + default y + help + Enable the VF610 ADC driver. diff --git a/drivers/adc/adc_shell.c b/drivers/adc/adc_shell.c index c86362327a..804f9cf2ad 100644 --- a/drivers/adc/adc_shell.c +++ b/drivers/adc/adc_shell.c @@ -33,6 +33,8 @@ #define DT_DRV_COMPAT nxp_kinetis_adc12 #elif DT_HAS_COMPAT_STATUS_OKAY(nxp_kinetis_adc16) #define DT_DRV_COMPAT nxp_kinetis_adc16 +#elif DT_HAS_COMPAT_STATUS_OKAY(nxp_vf610_adc) +#define DT_DRV_COMPAT nxp_vf610_adc #elif DT_HAS_COMPAT_STATUS_OKAY(st_stm32_adc) #define DT_DRV_COMPAT st_stm32_adc #elif DT_HAS_COMPAT_STATUS_OKAY(nuvoton_npcx_adc) diff --git a/drivers/adc/adc_vf610.c b/drivers/adc/adc_vf610.c new file mode 100644 index 0000000000..1fa8b090af --- /dev/null +++ b/drivers/adc/adc_vf610.c @@ -0,0 +1,275 @@ +/* + * Copyright (c) 2021 Antonio Tessarolo + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT nxp_vf610_adc + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(vf610_adc, CONFIG_ADC_LOG_LEVEL); + +#define ADC_CONTEXT_USES_KERNEL_TIMER +#include "adc_context.h" + +struct vf610_adc_config { + ADC_Type *base; + uint8_t clock_source; + uint8_t divide_ratio; + void (*irq_config_func)(const struct device *dev); +}; + +struct vf610_adc_data { + const struct device *dev; + struct adc_context ctx; + uint16_t *buffer; + uint16_t *repeat_buffer; + uint32_t channels; + uint8_t channel_id; +}; + +static int vf610_adc_channel_setup(const struct device *dev, + const struct adc_channel_cfg *channel_cfg) +{ + uint8_t channel_id = channel_cfg->channel_id; + + if (channel_id > (ADC_HC0_ADCH_MASK >> ADC_HC0_ADCH_SHIFT)) { + LOG_ERR("Channel %d is not valid", channel_id); + return -EINVAL; + } + + if (channel_cfg->acquisition_time != ADC_ACQ_TIME_DEFAULT) { + LOG_ERR("Invalid channel acquisition time"); + return -EINVAL; + } + + if (channel_cfg->differential) { + LOG_ERR("Differential channels are not supported"); + return -EINVAL; + } + + if (channel_cfg->gain != ADC_GAIN_1) { + LOG_ERR("Invalid channel gain"); + return -EINVAL; + } + + if (channel_cfg->reference != ADC_REF_INTERNAL) { + LOG_ERR("Invalid channel reference"); + return -EINVAL; + } + + return 0; +} + +static int start_read(const struct device *dev, const struct adc_sequence *sequence) +{ + const struct vf610_adc_config *config = dev->config; + struct vf610_adc_data *data = dev->data; + enum _adc_average_number mode; + enum _adc_resolution_mode resolution; + int error; + ADC_Type *base = config->base; + + switch (sequence->resolution) { + case 8: + resolution = adcResolutionBit8; + break; + case 10: + resolution = adcResolutionBit10; + break; + case 12: + resolution = adcResolutionBit12; + break; + default: + LOG_ERR("Invalid resolution"); + return -EINVAL; + } + + ADC_SetResolutionMode(base, resolution); + + switch (sequence->oversampling) { + case 0: + mode = adcAvgNumNone; + break; + case 2: + mode = adcAvgNum4; + break; + case 3: + mode = adcAvgNum8; + break; + case 4: + mode = adcAvgNum16; + break; + case 5: + mode = adcAvgNum32; + break; + default: + LOG_ERR("Invalid oversampling"); + return -EINVAL; + } + ADC_SetAverageNum(config->base, mode); + + data->buffer = sequence->buffer; + + adc_context_start_read(&data->ctx, sequence); + + error = adc_context_wait_for_completion(&data->ctx); + return error; +} + +static int vf610_adc_read(const struct device *dev, + const struct adc_sequence *sequence) +{ + struct vf610_adc_data *data = dev->data; + int error; + + adc_context_lock(&data->ctx, false, NULL); + error = start_read(dev, sequence); + adc_context_release(&data->ctx, error); + + return error; +} + +#ifdef CONFIG_ADC_ASYNC +static int vf610_adc_read_async(struct device *dev, + const struct adc_sequence *sequence, + struct k_poll_signal *async) +{ + struct vf610_adc_data *data = dev->driver_data; + int error; + + adc_context_lock(&data->ctx, true, async); + error = start_read(dev, sequence); + adc_context_release(&data->ctx, error); + + return error; +} +#endif + +static void vf610_adc_start_channel(const struct device *dev) +{ + const struct vf610_adc_config *config = dev->config; + struct vf610_adc_data *data = dev->data; + + data->channel_id = find_lsb_set(data->channels) - 1; + + LOG_DBG("Starting channel %d", data->channel_id); + + ADC_SetIntCmd(config->base, true); + + ADC_TriggerSingleConvert(config->base, data->channel_id); +} + +static void adc_context_start_sampling(struct adc_context *ctx) +{ + struct vf610_adc_data *data = + CONTAINER_OF(ctx, struct vf610_adc_data, ctx); + + data->channels = ctx->sequence.channels; + data->repeat_buffer = data->buffer; + + vf610_adc_start_channel(data->dev); +} + +static void adc_context_update_buffer_pointer(struct adc_context *ctx, + bool repeat_sampling) +{ + struct vf610_adc_data *data = + CONTAINER_OF(ctx, struct vf610_adc_data, ctx); + + if (repeat_sampling) { + data->buffer = data->repeat_buffer; + } +} + +static void vf610_adc_isr(void *arg) +{ + struct device *dev = (struct device *)arg; + const struct vf610_adc_config *config = dev->config; + struct vf610_adc_data *data = dev->data; + ADC_Type *base = config->base; + uint16_t result; + + result = ADC_GetConvertResult(base); + LOG_DBG("Finished channel %d. Result is 0x%04x", + data->channel_id, result); + + *data->buffer++ = result; + data->channels &= ~BIT(data->channel_id); + + if (data->channels) { + vf610_adc_start_channel(dev); + } else { + adc_context_on_sampling_done(&data->ctx, dev); + } +} + +static int vf610_adc_init(const struct device *dev) +{ + const struct vf610_adc_config *config = dev->config; + struct vf610_adc_data *data = dev->data; + ADC_Type *base = config->base; + adc_init_config_t adc_config; + + adc_config.averageNumber = adcAvgNumNone; + adc_config.resolutionMode = adcResolutionBit12; + adc_config.clockSource = config->clock_source; + adc_config.divideRatio = config->divide_ratio; + + ADC_Init(base, &adc_config); + + ADC_SetConvertTrigMode(base, adcSoftwareTrigger); + + ADC_SetCalibration(base, true); + + config->irq_config_func(dev); + data->dev = dev; + + adc_context_unlock_unconditionally(&data->ctx); + + return 0; +} + +static const struct adc_driver_api vf610_adc_driver_api = { + .channel_setup = vf610_adc_channel_setup, + .read = vf610_adc_read, +#ifdef CONFIG_ADC_ASYNC + .read_async = vf610_adc_read_async, +#endif +}; + +#define VF610_ADC_INIT(n) \ + static void vf610_adc_config_func_##n(const struct device *dev);\ + \ + static const struct vf610_adc_config vf610_adc_config_##n = { \ + .base = (ADC_Type *)DT_INST_REG_ADDR(n), \ + .clock_source = DT_INST_PROP(n, clk_source), \ + .divide_ratio = DT_INST_PROP(n, clk_divider), \ + .irq_config_func = vf610_adc_config_func_##n, \ + }; \ + \ + static struct vf610_adc_data vf610_adc_data_##n = { \ + ADC_CONTEXT_INIT_TIMER(vf610_adc_data_##n, ctx), \ + ADC_CONTEXT_INIT_LOCK(vf610_adc_data_##n, ctx), \ + ADC_CONTEXT_INIT_SYNC(vf610_adc_data_##n, ctx), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(n, &vf610_adc_init, \ + NULL, &vf610_adc_data_##n, \ + &vf610_adc_config_##n, POST_KERNEL, \ + CONFIG_ADC_INIT_PRIORITY, \ + &vf610_adc_driver_api); \ + \ + static void vf610_adc_config_func_##n(const struct device *dev) \ + { \ + IRQ_CONNECT(DT_INST_IRQN(n), DT_INST_IRQ(n, priority), \ + vf610_adc_isr, \ + DEVICE_DT_INST_GET(n), 0); \ + \ + irq_enable(DT_INST_IRQN(n)); \ + } + +DT_INST_FOREACH_STATUS_OKAY(VF610_ADC_INIT) diff --git a/dts/arm/nxp/nxp_imx6sx_m4.dtsi b/dts/arm/nxp/nxp_imx6sx_m4.dtsi index 85940325e4..4a6d606c25 100644 --- a/dts/arm/nxp/nxp_imx6sx_m4.dtsi +++ b/dts/arm/nxp/nxp_imx6sx_m4.dtsi @@ -430,6 +430,34 @@ #pwm-cells = <2>; status = "disabled"; }; + + adc1: adc@42280000 { + compatible = "nxp,vf610-adc"; + reg = <0x42280000 0x4000>; + clk-source = <1>; + clk-divider = <2>; + interrupts = <100 0>; + rdc = <(RDC_DOMAIN_PERM(A9_DOMAIN_ID,\ + RDC_DOMAIN_PERM_RW)|\ + RDC_DOMAIN_PERM(M4_DOMAIN_ID,\ + RDC_DOMAIN_PERM_RW))>; + status = "disabled"; + #io-channel-cells = <1>; + }; + + adc2: adc@42284000 { + compatible = "nxp,vf610-adc"; + reg = <0x42284000 0x4000>; + clk-source = <1>; + clk-divider = <2>; + interrupts = <101 0>; + rdc = <(RDC_DOMAIN_PERM(A9_DOMAIN_ID,\ + RDC_DOMAIN_PERM_RW)|\ + RDC_DOMAIN_PERM(M4_DOMAIN_ID,\ + RDC_DOMAIN_PERM_RW))>; + status = "disabled"; + #io-channel-cells = <1>; + }; }; }; diff --git a/dts/bindings/iio/adc/nxp,vf610-adc.yaml b/dts/bindings/iio/adc/nxp,vf610-adc.yaml new file mode 100644 index 0000000000..fcba99c772 --- /dev/null +++ b/dts/bindings/iio/adc/nxp,vf610-adc.yaml @@ -0,0 +1,50 @@ +# Copyright (c) 2021, Antonio Tessarolo +# SPDX-License-Identifier: Apache-2.0 + +description: Vf610 Adc + +compatible: "nxp,vf610-adc" + +include: adc-controller.yaml + +properties: + reg: + required: true + + interrupts: + required: true + + clk-source: + type: int + required: true + description: | + Select adc clock source: 0 clock from IPG, 1 clock from IPG divided 2, 2 async clock + + clk-divider: + type: int + required: true + description: | + Select clock divider: 0 clock divided by 1, 1 clock divided by 2, 2 clock divided by 4, + 3 clock divided by 8 + + "#io-channel-cells": + const: 1 + + rdc: + type: int + required: true + description: | + Set the RDC permission for this peripheral: the RDC controls which + processor can access to this peripheral. User can select to assign this + peripheral to the M4 processor, A9 processor or both with R/W or RW + permissions. To set wanted permission a user should use the helper + macro RDC_DOMAIN_PERM(domain,permission) where domain must be one of + M4_DOMAIN_ID or A9_DOMAIN_ID and permission one among + RDC_DOMAIN_PERM_NONE, RDC_DOMAIN_PERM_W, RDC_DOMAIN_PERM_R, + RDC_DOMAIN_PERM_RW. Example to allow both processor to read/write to + this peripheral a user should put: + rdc = <(RDC_DOMAIN_PERM(A9_DOMAIN_ID, RDC_DOMAIN_PERM_RW) | + RDC_DOMAIN_PERM(M4_DOMAIN_ID, RDC_DOMAIN_PERM_RW))>; + +io-channel-cells: + - input diff --git a/soc/arm/nxp_imx/mcimx6x_m4/soc.c b/soc/arm/nxp_imx/mcimx6x_m4/soc.c index dc10b5c300..c4e7742a29 100644 --- a/soc/arm/nxp_imx/mcimx6x_m4/soc.c +++ b/soc/arm/nxp_imx/mcimx6x_m4/soc.c @@ -139,6 +139,14 @@ static void SOC_RdcInit(void) /* Set access to PWM-8 for M4 core */ RDC_SetPdapAccess(RDC, rdcPdapPwm8, RDC_DT_VAL(pwm8), false, false); #endif +#if DT_NODE_HAS_STATUS(DT_NODELABEL(adc1), okay) + /* Set access to ADC-1 for M4 core */ + RDC_SetPdapAccess(RDC, rdcPdapAdc1, RDC_DT_VAL(adc1), false, false); +#endif +#if DT_NODE_HAS_STATUS(DT_NODELABEL(adc2), okay) + /* Set access to ADC-2 for M4 core */ + RDC_SetPdapAccess(RDC, rdcPdapAdc2, RDC_DT_VAL(adc2), false, false); +#endif } /* Initialize cache. */