diff --git a/boards/rpi-pico/Kconfig b/boards/rpi-pico/Kconfig index d1a6ca0479..dd6e0d4141 100644 --- a/boards/rpi-pico/Kconfig +++ b/boards/rpi-pico/Kconfig @@ -12,6 +12,7 @@ config BOARD_RPI_PICO bool default y select CPU_MODEL_RP2040 + select HAS_PERIPH_ADC select HAS_PERIPH_UART select HAVE_SAUL_GPIO diff --git a/boards/rpi-pico/Makefile.features b/boards/rpi-pico/Makefile.features index ddca0fd849..57455378e5 100644 --- a/boards/rpi-pico/Makefile.features +++ b/boards/rpi-pico/Makefile.features @@ -1,5 +1,6 @@ CPU := rpx0xx # Put defined MCU peripherals here (in alphabetical order) +FEATURES_PROVIDED += periph_adc FEATURES_PROVIDED += periph_timer FEATURES_PROVIDED += periph_uart diff --git a/boards/rpi-pico/include/periph_conf.h b/boards/rpi-pico/include/periph_conf.h index 6e2741260d..d8f99513cc 100644 --- a/boards/rpi-pico/include/periph_conf.h +++ b/boards/rpi-pico/include/periph_conf.h @@ -78,6 +78,21 @@ static const timer_conf_t timer_config[] = { #define TIMER_NUMOF ARRAY_SIZE(timer_config) +/** + * @name ADC configuration + * + * The configuration consists simply of a list of channels that should be used + * @{ + */ +static const adc_conf_t adc_config[] = { + { .pin = GPIO_PIN(0, 26), .chan = 0}, + { .pin = GPIO_PIN(0, 27), .chan = 1}, + { .pin = GPIO_PIN(0, 28), .chan = 2}, + }; + +#define ADC_NUMOF ARRAY_SIZE(adc_config) +/** @} */ + #ifdef __cplusplus } #endif diff --git a/cpu/rpx0xx/clock.c b/cpu/rpx0xx/clock.c index 424632b263..3af4252951 100644 --- a/cpu/rpx0xx/clock.c +++ b/cpu/rpx0xx/clock.c @@ -177,3 +177,17 @@ void clock_gpout3_configure(uint32_t f_in, uint32_t f_out, CLOCKS_CLK_GPOUT3_CTR io_reg_atomic_set(&PADS_BANK0->GPIO25, 1U << PADS_BANK0_GPIO25_IE_Pos); gpio_set_function_select(25, FUNCTION_SELECT_CLOCK); } + +void clock_adc_configure(CLOCKS_CLK_ADC_CTRL_AUXSRC_Enum aux) +{ + /* Stop the clock generator */ + io_reg_atomic_clear(&CLOCKS->CLK_ADC_CTRL, + (1u << CLOCKS_CLK_ADC_CTRL_ENABLE_Pos)); + /* Selects the new auxiliary clock source */ + io_reg_write_dont_corrupt(&CLOCKS->CLK_ADC_CTRL, + aux << CLOCKS_CLK_ADC_CTRL_AUXSRC_Pos, + CLOCKS_CLK_ADC_CTRL_AUXSRC_Msk); + /* Restart the clock generator */ + io_reg_atomic_set(&CLOCKS->CLK_ADC_CTRL, + (1u << CLOCKS_CLK_ADC_CTRL_ENABLE_Pos)); +} diff --git a/cpu/rpx0xx/cpu.c b/cpu/rpx0xx/cpu.c index 61fd73f773..6fa88532d5 100644 --- a/cpu/rpx0xx/cpu.c +++ b/cpu/rpx0xx/cpu.c @@ -41,6 +41,7 @@ static void _cpu_reset(void) | RESETS_RESET_pll_usb_Msk | RESETS_RESET_pll_sys_Msk | RESETS_RESET_pads_qspi_Msk + | RESETS_RESET_pads_bank0_Msk | RESETS_RESET_io_qspi_Msk); periph_reset(rst); /* Assert that reset has completed except for those components which @@ -78,6 +79,13 @@ static void _cpu_reset(void) clock_gpout0_configure(CLOCK_XOSC, CLOCK_XOSC, CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_clk_ref); } + + /* Configure USB PLL to deliver 48MHz needed by ADC */ + if (IS_USED(MODULE_PERIPH_ADC)) { + pll_start_usb(PLL_USB_REF_DIV, PLL_USB_VCO_FEEDBACK_SCALE, + PLL_USB_POSTDIV1, PLL_USB_POSTDIV2); + clock_adc_configure(CLOCKS_CLK_ADC_CTRL_AUXSRC_clksrc_pll_usb); + } } void cpu_init(void) diff --git a/cpu/rpx0xx/include/periph_cpu.h b/cpu/rpx0xx/include/periph_cpu.h index bd6d40ff18..a7236d0c3e 100644 --- a/cpu/rpx0xx/include/periph_cpu.h +++ b/cpu/rpx0xx/include/periph_cpu.h @@ -390,6 +390,14 @@ typedef struct { } gpio_io_ctrl_t; /** @} */ +/** + * @brief ADC channel configuration data + */ +typedef struct { + gpio_t pin; /**< Pin connected to the channel */ + uint8_t chan; /**< CPU ADC channel connected to the pin */ +} adc_conf_t; + /** * @brief Configuration details for an UART interface needed by the RPX0XX peripheral */ @@ -613,6 +621,13 @@ void clock_gpout2_configure(uint32_t f_in, uint32_t f_out, CLOCKS_CLK_GPOUT2_CTR */ void clock_gpout3_configure(uint32_t f_in, uint32_t f_out, CLOCKS_CLK_GPOUT3_CTRL_AUXSRC_Enum aux); +/** + * @brief Configure the ADC clock to run from a dedicated auxiliary + * clock source + * + * @param aux Auxiliary clock source + */ +void clock_adc_configure(CLOCKS_CLK_ADC_CTRL_AUXSRC_Enum aux); /** @} */ /** @@ -636,7 +651,7 @@ void pll_start_sys(uint8_t ref_div, uint8_t post_div_1, uint8_t post_div_2); /** - * @brief Start the PLL for the system clock + * @brief Start the PLL for the USB clock * output[MHz] = f_ref / @p ref_div * @p vco_feedback_scale / @p post_div_1 / @p post_div_2 * * @note Usual setting should be (12 MHz, 1, 40, 5, 2) to get a 48 MHz USB clock signal diff --git a/cpu/rpx0xx/periph/adc.c b/cpu/rpx0xx/periph/adc.c new file mode 100644 index 0000000000..68eebac190 --- /dev/null +++ b/cpu/rpx0xx/periph/adc.c @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2023 Mesotic SAS + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup cpu_rpx0xx + * @ingroup drivers_periph_adc + * @{ + * + * @file + * @brief Low-level ADC driver implementation + * + * @author Dylan Laduranty + * @} + */ + +#include +#include "cpu.h" +#include "mutex.h" +#include "periph/adc.h" +#include "periph_conf.h" + +/** + * @brief Lock to prevent concurrency issues when used from different threads + */ +static mutex_t lock; + +static inline void _prep(void) +{ + mutex_lock(&lock); +} + +static inline void _done(void) +{ + mutex_unlock(&lock); +} + +static void _disable_digital_func(gpio_t pin) +{ + + const gpio_pad_ctrl_t disable_digital_pad = { + .input_enable = 0, + .output_disable = 1, + }; + + gpio_set_pad_config(pin, disable_digital_pad); +} + +int adc_init(adc_t line) +{ + if (line >= ADC_NUMOF) { + return -ENODEV; + } + /* Lock mutex for exclusive access */ + _prep(); + /* Reset ADC peripheral */ + periph_reset(RESETS_RESET_adc_Msk); + periph_reset_done(RESETS_RESET_adc_Msk); + + /* Enable ADC peripheral and its clock */ + io_reg_atomic_set(&ADC->CS, 1 << ADC_CS_EN_Pos); + /* Disable associated GPIO functionality as requested by datasheet */ + _disable_digital_func(adc_config[line].pin); + /* Unlock mutex */ + _done(); + return 0; +} + +int32_t adc_sample(adc_t line, adc_res_t res) +{ + int val = 0; + + /* rpx0xx MCU only supports 12 bits resolution */ + if (res != ADC_RES_12BIT) { + return -EINVAL; + } + /* Lock mutex for exclusive access */ + _prep(); + /* Select the channel */ + io_reg_write_dont_corrupt(&ADC->CS, adc_config[line].chan << ADC_CS_AINSEL_Pos, + ADC_CS_AINSEL_Msk); + /* Wait for ADC to be ready */ + while (!(ADC->CS & ADC_CS_READY_Msk)) {} + /* Trigger one-shot sample */ + io_reg_atomic_set(&ADC->CS, 1 << ADC_CS_START_ONCE_Pos); + /* Wait for completion */ + while (!(ADC->CS & ADC_CS_READY_Msk)) {} + /* Get the result */ + val = ADC->RESULT & ADC_RESULT_RESULT_Msk; + /* Unlock mutex */ + _done(); + return val; +}