Commit 45a8a0f0 authored by Julien Panis's avatar Julien Panis Committed by Benjamin Cabé
Browse files

drivers: adc: cc23x0: Add support for DMA mode

The ADC has a dedicated interface for communicating with the DMA.
The ADC module provides four interrupt sources (one for each
conversion result storage register) which can be configured to
source the DMA trigger.

Signed-off-by: default avatarJulien Panis <jpanis@baylibre.com>
parent 6dcf2fdf
Loading
Loading
Loading
Loading
+10 −0
Changes for drivers/adc/Kconfig.cc23x0: 10 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -8,3 +8,13 @@ config ADC_CC23X0
	select PINCTRL
	help
	  Enable the TI CC23X0 ADC driver.

config ADC_CC23X0_DMA_DRIVEN
	bool "DMA support for TI CC23X0 ADC devices"
	depends on ADC_CC23X0
	select DMA
	help
	  DMA driven mode offloads data transfer tasks from the CPU
	  and requires fewer interrupts to handle the ADC sequence mode.
	  When DMA mode is used, the ADC internal gain is not compensated
	  for measurement adjustment.
+94 −0
Changes for drivers/adc/adc_cc23x0.c: 94 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -11,6 +11,7 @@ LOG_MODULE_REGISTER(adc_cc23x0, CONFIG_ADC_LOG_LEVEL);

#include <zephyr/device.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/dma.h>
#include <zephyr/drivers/pinctrl.h>
#include <zephyr/irq.h>
#include <zephyr/sys/util.h>
@@ -18,6 +19,8 @@ LOG_MODULE_REGISTER(adc_cc23x0, CONFIG_ADC_LOG_LEVEL);
#include <driverlib/adc.h>
#include <driverlib/clkctl.h>

#include <inc/hw_memmap.h>

#define ADC_CONTEXT_USES_KERNEL_TIMER
#include "adc_context.h"

@@ -31,10 +34,15 @@ LOG_MODULE_REGISTER(adc_cc23x0, CONFIG_ADC_LOG_LEVEL);

#define ADC_CC23X0_MAX_CYCLES	1023

#ifdef CONFIG_ADC_CC23X0_DMA_DRIVEN
#define ADC_CC23X0_REG_GET(offset) (ADC_BASE + (offset))
#define ADC_CC23X0_INT_MASK	ADC_INT_DMADONE
#else
#define ADC_CC23X0_INT_MASK	(ADC_INT_MEMRES_00 | \
				 ADC_INT_MEMRES_01 | \
				 ADC_INT_MEMRES_02 | \
				 ADC_INT_MEMRES_03)
#endif

#define ADC_CC23X0_INT_MEMRES(i)	(ADC_INT_MEMRES_00 << (i))

@@ -46,6 +54,11 @@ struct adc_cc23x0_config {
	const struct pinctrl_dev_config *pincfg;
	void (*irq_cfg_func)(void);
	uint32_t base;
#ifdef CONFIG_ADC_CC23X0_DMA_DRIVEN
	const struct device *dma_dev;
	uint8_t dma_channel;
	uint8_t dma_trigsrc;
#endif
};

struct adc_cc23x0_data {
@@ -64,8 +77,43 @@ struct adc_cc23x0_data {
static void adc_context_start_sampling(struct adc_context *ctx)
{
	struct adc_cc23x0_data *data = CONTAINER_OF(ctx, struct adc_cc23x0_data, ctx);
#ifdef CONFIG_ADC_CC23X0_DMA_DRIVEN
	const struct adc_cc23x0_config *cfg = data->dev->config;

	struct dma_block_config block_cfg = {
		.source_address = ADC_CC23X0_REG_GET(ADC_O_MEMRES0),
		.dest_address = (uint32_t)(data->buffer),
		.source_addr_adj = DMA_ADDR_ADJ_INCREMENT,
		.dest_addr_adj = DMA_ADDR_ADJ_INCREMENT,
		.block_size = data->ch_count * sizeof(*data->buffer),
	};

	struct dma_config dma_cfg = {
		.dma_slot = cfg->dma_trigsrc,
		.channel_direction = PERIPHERAL_TO_MEMORY,
		.block_count = 1,
		.head_block = &block_cfg,
		.source_data_size = sizeof(uint32_t),
		.dest_data_size = sizeof(*data->buffer),
		.source_burst_length = block_cfg.block_size,
		.dma_callback = NULL,
		.user_data = NULL,
	};

	int ret;

	ret = dma_config(cfg->dma_dev, cfg->dma_channel, &dma_cfg);
	if (ret) {
		LOG_ERR("Failed to configure DMA (%d)", ret);
		return;
	}

	ADCEnableDMATrigger();

	dma_start(cfg->dma_dev, cfg->dma_channel);
#else
	data->mem_index = 0;
#endif

	ADCManualTrigger();
}
@@ -82,9 +130,22 @@ static void adc_context_update_buffer_pointer(struct adc_context *ctx, bool repe
static void adc_cc23x0_isr(const struct device *dev)
{
	struct adc_cc23x0_data *data = dev->data;
#ifndef CONFIG_ADC_CC23X0_DMA_DRIVEN
	uint32_t adc_val;
	uint8_t ch;
#endif

#ifdef CONFIG_ADC_CC23X0_DMA_DRIVEN
	/*
	 * In DMA mode, do not compensate for the ADC internal gain with
	 * ADCAdjustValueForGain() function. To perform this compensation,
	 * reading the data from the buffer and overwriting them would be
	 * necessary, which would mitigate the advantage of using DMA.
	 */
	ADCClearInterrupt(ADC_INT_DMADONE);
	LOG_DBG("DMA done");
	adc_context_on_sampling_done(&data->ctx, dev);
#else
	/*
	 * Even when there are multiple channels, only 1 flag can be set because
	 * of the trigger policy (next conversion requires a trigger)
@@ -115,6 +176,7 @@ static void adc_cc23x0_isr(const struct device *dev)
	} else {
		adc_context_on_sampling_done(&data->ctx, dev);
	}
#endif
}

static int adc_cc23x0_read_common(const struct device *dev,
@@ -122,7 +184,9 @@ static int adc_cc23x0_read_common(const struct device *dev,
				  bool asynchronous,
				  struct k_poll_signal *sig)
{
#ifndef CONFIG_ADC_CC23X0_DMA_DRIVEN
	const struct adc_cc23x0_config *cfg = dev->config;
#endif
	struct adc_cc23x0_data *data = dev->data;
	uint32_t bitmask;
	uint8_t ch_start = ADC_CC23X0_CH_UNDEF;
@@ -165,6 +229,10 @@ static int adc_cc23x0_read_common(const struct device *dev,

		/* Set adjustment offset for this channel */
		ADCSetAdjustmentOffset(data->ref_volt[ch_start]);

#ifdef CONFIG_ADC_CC23X0_DMA_DRIVEN
		ADCEnableDMAInterrupt(ADC_CC23X0_INT_MEMRES(0));
#endif
	} else if (data->ch_count <= ADC_CC23X0_MEM_COUNT) {
		for (i = 0; i < ADC_CC23X0_CH_COUNT; i++) {
			if (!(bitmask & BIT(i))) {
@@ -180,8 +248,10 @@ static int adc_cc23x0_read_common(const struct device *dev,
			/* Set input channel */
			ADCSetInput(data->ref_volt[i], i, mem_index);

#ifndef CONFIG_ADC_CC23X0_DMA_DRIVEN
			/* Set trigger policy so next conversion requires a trigger */
			ADC_CC23X0_MEMCTL(cfg->base, mem_index) |= ADC_MEMCTL0_TRG;
#endif

			mem_index++;
		}
@@ -192,6 +262,14 @@ static int adc_cc23x0_read_common(const struct device *dev,

		/* Set adjustment offset for the first channel */
		ADCSetAdjustmentOffset(data->ref_volt[ch_start]);

#ifdef CONFIG_ADC_CC23X0_DMA_DRIVEN
		/*
		 * DMA transfer will be triggered when the last storage register
		 * of the sequence is loaded with a new conversion result
		 */
		ADCEnableDMAInterrupt(ADC_CC23X0_INT_MEMRES(mem_index - 1));
#endif
	} else {
		LOG_ERR("Too many channels in the sequence, max %u", ADC_CC23X0_MEM_COUNT);
		return -EINVAL;
@@ -408,6 +486,12 @@ static int adc_cc23x0_init(const struct device *dev)
	/* Enable interrupts */
	ADCEnableInterrupt(ADC_CC23X0_INT_MASK);

#ifdef CONFIG_ADC_CC23X0_DMA_DRIVEN
	if (!device_is_ready(cfg->dma_dev)) {
		return -ENODEV;
	}
#endif

	adc_context_unlock_unconditionally(&data->ctx);

	return 0;
@@ -422,6 +506,15 @@ static const struct adc_driver_api adc_cc23x0_driver_api = {
	.ref_internal = 1400,
};

#ifdef CONFIG_ADC_CC23X0_DMA_DRIVEN
#define ADC_CC23X0_DMA_INIT(n)						\
	.dma_dev = DEVICE_DT_GET(TI_CC23X0_DT_INST_DMA_CTLR(n, dma)),	\
	.dma_channel = TI_CC23X0_DT_INST_DMA_CHANNEL(n, dma),		\
	.dma_trigsrc = TI_CC23X0_DT_INST_DMA_TRIGSRC(n, dma),
#else
#define ADC_CC23X0_DMA_INIT(n)
#endif

#define CC23X0_ADC_INIT(n)							\
	PINCTRL_DT_INST_DEFINE(n);						\
										\
@@ -438,6 +531,7 @@ static const struct adc_driver_api adc_cc23x0_driver_api = {
		.pincfg = PINCTRL_DT_INST_DEV_CONFIG_GET(n),			\
		.irq_cfg_func = adc_cc23x0_cfg_func_##n,			\
		.base = DT_INST_REG_ADDR(n),					\
		ADC_CC23X0_DMA_INIT(n)						\
	};									\
										\
	static struct adc_cc23x0_data adc_cc23x0_data_##n = {			\
+15 −0
Changes for dts/bindings/adc/ti,cc23x0-adc.yaml: 15 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -23,5 +23,20 @@ properties:
  "#io-channel-cells":
    const: 1

  dmas:
    description: |
      Optional DMA specifier. The specifier will have a phandle reference to the
      DMA controller, the channel number, and the peripheral trigger source.

      Example for channel 3 with adc0trg trigger source:
        dmas = <&dma 3 5>;

  dma-names:
    description: |
      Required if the dmas property exists. This should be "dma".

      Example:
        dma-names = "dma";

io-channel-cells:
  - input