From aa6ec6518c218c71f9e205f415b76317dbc75ad3 Mon Sep 17 00:00:00 2001 From: curuvar <58759586+curuvar@users.noreply.github.com> Date: Thu, 7 Jul 2022 07:10:58 -0400 Subject: [PATCH] Added ADC to RP2040 --- .gitignore | 4 +- arch/arm/src/rp2040/Make.defs | 4 + arch/arm/src/rp2040/hardware/rp2040_adc.h | 120 ++++ arch/arm/src/rp2040/rp2040_adc.c | 652 ++++++++++++++++++ arch/arm/src/rp2040/rp2040_adc.h | 108 +++ arch/arm/src/rp2040/rp2040_ws2812.h | 2 +- boards/Kconfig | 3 + .../rp2040/adafruit-feather-rp2040/README.txt | 1 + .../src/rp2040_bringup.c | 45 ++ boards/arm/rp2040/adafruit-kb2040/README.txt | 1 + .../adafruit-kb2040/src/rp2040_bringup.c | 45 ++ boards/arm/rp2040/common/Kconfig | 54 ++ .../arm/rp2040/pimoroni-tiny2040/README.txt | 1 + .../pimoroni-tiny2040/src/rp2040_bringup.c | 47 ++ boards/arm/rp2040/raspberrypi-pico/README.txt | 1 + .../raspberrypi-pico/src/rp2040_bringup.c | 47 ++ drivers/analog/adc.c | 8 +- 17 files changed, 1137 insertions(+), 6 deletions(-) create mode 100644 arch/arm/src/rp2040/hardware/rp2040_adc.h create mode 100644 arch/arm/src/rp2040/rp2040_adc.c create mode 100644 arch/arm/src/rp2040/rp2040_adc.h create mode 100644 boards/arm/rp2040/common/Kconfig diff --git a/.gitignore b/.gitignore index 658e3c09b5..f08b12a7f3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,10 +26,12 @@ *.sym *.wsp *.ddc +*.dds *~ .depend /.config -/.config.old +/.config.* +/.config-* /.config\ * /.cproject /.gdbinit diff --git a/arch/arm/src/rp2040/Make.defs b/arch/arm/src/rp2040/Make.defs index 7bd096d8c6..6f7e129df4 100644 --- a/arch/arm/src/rp2040/Make.defs +++ b/arch/arm/src/rp2040/Make.defs @@ -70,6 +70,10 @@ ifeq ($(CONFIG_WS2812),y) CHIP_CSRCS += rp2040_ws2812.c endif +ifeq ($(CONFIG_ADC),y) +CHIP_CSRCS += rp2040_adc.c +endif + ifeq ($(CONFIG_RP2040_FLASH_BOOT),y) ifneq ($(PICO_SDK_PATH),) include chip/boot2/Make.defs diff --git a/arch/arm/src/rp2040/hardware/rp2040_adc.h b/arch/arm/src/rp2040/hardware/rp2040_adc.h new file mode 100644 index 0000000000..96225128b2 --- /dev/null +++ b/arch/arm/src/rp2040/hardware/rp2040_adc.h @@ -0,0 +1,120 @@ +/**************************************************************************** + * arch/arm/src/rp2040/hardware/rp2040_adc.h + * + * Generated from rp2040.svd originally provided by + * Raspberry Pi (Trading) Ltd. + * + * Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_RP2040_HARDWARE_RP2040_ADC_H +#define __ARCH_ARM_SRC_RP2040_HARDWARE_RP2040_ADC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "hardware/rp2040_memorymap.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Register offsets *********************************************************/ + +#define RP2040_ADC_CS_OFFSET 0x000000 /* ADC Control and Status register */ +#define RP2040_ADC_RESULT_OFFSET 0x000004 /* ADC Results register */ +#define RP2040_ADC_FCS_OFFSET 0x000008 /* ADC FIFO Control and Status register */ +#define RP2040_ADC_FIFO_OFFSET 0x00000c /* ADC Result FIFO */ +#define RP2040_ADC_DIV_OFFSET 0x000010 /* ADC Clock Divider register */ +#define RP2040_ADC_INTR_OFFSET 0x000014 /* ADC Raw Interrupts register */ +#define RP2040_ADC_INTE_OFFSET 0x000018 /* ADC Interrupt Enable register */ +#define RP2040_ADC_INTF_OFFSET 0x00001c /* ADC Interrupt Force register */ +#define RP2040_ADC_INTS_OFFSET 0x000020 /* ADC Interrupt Status register */ + +/* Register definitions *****************************************************/ + +#define RP2040_ADC_CS (RP2040_ADC_BASE + RP2040_ADC_CS_OFFSET) +#define RP2040_ADC_RESULT (RP2040_ADC_BASE + RP2040_ADC_RESULT_OFFSET) +#define RP2040_ADC_FCS (RP2040_ADC_BASE + RP2040_ADC_FCS_OFFSET) +#define RP2040_ADC_FIFO (RP2040_ADC_BASE + RP2040_ADC_FIFO_OFFSET) +#define RP2040_ADC_DIV (RP2040_ADC_BASE + RP2040_ADC_DIV_OFFSET) +#define RP2040_ADC_INTR (RP2040_ADC_BASE + RP2040_ADC_INTR_OFFSET) +#define RP2040_ADC_INTE (RP2040_ADC_BASE + RP2040_ADC_INTE_OFFSET) +#define RP2040_ADC_INTF (RP2040_ADC_BASE + RP2040_ADC_INTF_OFFSET) +#define RP2040_ADC_INTS (RP2040_ADC_BASE + RP2040_ADC_INTS_OFFSET) + +/* Register bit definitions *************************************************/ + +#define RP2040_ADC_CS_RROBIN_SHIFT (16) +#define RP2040_ADC_CS_RROBIN_MASK (0x001fl << RPC2040_ADC_CS_RROBIN_SHIFT) +#define RP2040_ADC_CS_AINSEL_SHIFT (12) +#define RP2040_ADC_CS_AINSEL_MASK (0x0007l << RPC2040_ADC_CS_AINSEL_SHIFT) +#define RP2040_ADC_CS_ERR_STICKY (1 << 10) +#define RP2040_ADC_CS_ERR (1 << 9) +#define RP2040_ADC_CS_READY (1 << 8) +#define RP2040_ADC_CS_START_MANY (1 << 3) +#define RP2040_ADC_CS_START_ONCE (1 << 2) +#define RP2040_ADC_CS_TS_ENA (1 << 1) +#define RP2040_ADC_CS_EN (1 << 0) + +#define RP2040_ADC_RESULT_VAL_MASK (0x00000fffl) + +#define RP2040_ADC_FCS_THRESH_SHIFT (24) +#define RP2040_ADC_FCS_THRESH_MASK (0x000fl << RP2040_ADC_FCS_THRESH_SHIFT) +#define RP2040_ADC_FCS_LEVEL_SHIFT (16) +#define RP2040_ADC_FCS_LEVEL_MASK (0x000fl << RP2040_ADC_FCS_LEVEL_SHIFT) +#define RP2040_ADC_FCS_OVER (1 << 11) +#define RP2040_ADC_FCS_UNDER (1 << 10) +#define RP2040_ADC_FCS_FULL (1 << 9) +#define RP2040_ADC_FCS_EMPTY (1 << 8) +#define RP2040_ADC_FCS_DREQ_EN (1 << 3) +#define RP2040_ADC_FCS_ERR (1 << 2) +#define RP2040_ADC_FCS_SHIFT (1 << 1) +#define RP2040_ADC_FCS_EN (1 << 0) + +#define RP2040_ADC_FIFO_ERR (1 << 15) +#define RP2040_ADC_FIFO_VAL_MASK (0x0fffl) + +#define RP2040_ADC_DIV_INT_SHIFT (8) +#define RP2040_ADC_DIV_INT_MASK (0x0fffl << RP2040_ADC_DIV_INT_SHIFT) +#define RP2040_ADC_DIV_FRAC_SHIFT (0) +#define RP2040_ADC_DIV_FRAC_MASK (0x00ffl << RP2040_ADC_DIV_FRAC_MASK) + +#define RP2040_ADC_INTR_FIFO (1 << 0) /* Raw interrupt status */ + +#define RP2040_ADC_INTE_FIFO (1 << 0) /* Set to pass interrupts */ + +#define RP2040_ADC_INTF_FIFO (1 << 0) /* Write 1 to force the interrupt. */ + +#define RP2040_ADC_INTS_FIFO (1 << 0) /* Masked interrupt status */ + +#endif /* __ARCH_ARM_SRC_RP2040_HARDWARE_RP2040_ADC_H */ diff --git a/arch/arm/src/rp2040/rp2040_adc.c b/arch/arm/src/rp2040/rp2040_adc.c new file mode 100644 index 0000000000..cdbb54599a --- /dev/null +++ b/arch/arm/src/rp2040/rp2040_adc.c @@ -0,0 +1,652 @@ +/**************************************************************************** + * arch/arm/src/rp2040/rp2040_adc.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Notes: + * The ADC driver upper-half returns signed values of up to 32-bits to + * the user and expects the high-order bits in any result to be + * significant. So, while the RP2040 hardware returns an unsigned value + * in the low-order 12 bits of the result register, we shift this + * value to the high-order bits. + * + * The result is that to convert a 32-bit value returned from the ADC + * driver, you should use V = ADC_AVDD * value / (2^31) where ADC_AVDD + * is the analogue reference voltage supplied to the RP2040 chip. If + * 8 or 16 bit values are returned the divisor would be (2^15) or (2^7) + * respectively. + * + * Also, if the conversion error bit was set for a particular sample, + * the return value will be negated. Any negative return value should + * be treated as erroneous. + * + * ------------- + * + * This lower-half supports multiple drivers (/dev/adc0, /dav/dca1, etc.) + * that each may read data from any of the ADC ports. The driver reads + * whichever ADC ports are needed by ANY of ther drivers in strict + * round-robin fashion, passing the converted values to the drivers that + * needed it. Data is only passed if the driver is open. + * + * -------------- + * + * This code reads the ADC ports at full speed. At the time this comment + * was written, the upper-half will throw away any converted values it + * receives when the buffer is full; therefor, if the data is not read + * for a while, the returned values may be stale when finally read. You + * can use the ANIOC_RESET_FIFO ioctl call to flush this stale data. + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include "arm_internal.h" + +#include + +#include + +#include +#include + +#ifdef CONFIG_RP2040_ADC + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define ADC_CHANNEL_COUNT 5 +#define ADC_TEMP_CHANNEL 4 + +/* Get the private data pointer from a device pointer */ + +#define PRIV(x) ((FAR struct private_s *)(x)->ad_priv) + +/**************************************************************************** + * Private Type Definitions + ****************************************************************************/ + +struct private_s +{ + FAR const struct adc_callback_s *callback; /* ADC Callback Structure */ + FAR FAR struct adc_dev_s *next_device; + FAR FAR struct adc_dev_s *prior_device; + bool has_channel[ADC_CHANNEL_COUNT]; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int interrupt_handler(int irq, + FAR void *context, + FAR void *arg); + +static void get_next_channel(void); +static void add_device(FAR struct adc_dev_s *dev); +static void remove_device(FAR struct adc_dev_s *dev); + +/* ADC methods */ + +static int my_bind(FAR struct adc_dev_s *dev, + FAR const struct adc_callback_s *callback); + +static void my_reset(FAR struct adc_dev_s *dev); +static int my_setup(FAR struct adc_dev_s *dev); +static void my_shutdown(FAR struct adc_dev_s *dev); +static void my_rxint(FAR struct adc_dev_s *dev, bool enable); +static int my_ioctl(FAR struct adc_dev_s *dev, int cmd, unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct adc_ops_s g_adcops = +{ + .ao_bind = my_bind, /* Called first during initialization. */ + .ao_reset = my_reset, /* Called second during intialization. */ + .ao_setup = my_setup, /* Called during first open. */ + .ao_shutdown = my_shutdown, /* Called during last close. */ + .ao_rxint = my_rxint, /* Called to enable/disable interrupts. */ + .ao_ioctl = my_ioctl, /* Called for custom ioctls. */ +}; + +static const int8_t g_gpio_map[ADC_CHANNEL_COUNT] = +{ + 26, 27, 28, 29, -1 +}; + +static FAR struct adc_dev_s *g_first_device = NULL; + +static uint8_t g_current_channel = 0xf0; /* too big */ +static uint8_t g_active_count = 0; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: interrupt_handler + * + * Description: + * ADC interrupt handler. Note that this one handler is shared between + * all ADC devices. + * + * Note: This is called from inside an interrupt service routine. + ****************************************************************************/ + +static int interrupt_handler(int irq, void *context, void *arg) +{ + FAR struct adc_dev_s *a_device; + FAR const struct adc_callback_s *a_callback; + int32_t value; + bool error_bit_set; + + if (g_active_count == 0) + { + /* Last device has been removed -- turn off ADC */ + + putreg32(0, RP2040_ADC_CS); + + putreg32(0, RP2040_ADC_INTE); + + up_disable_irq(RP2040_ADC_IRQ_FIFO); + + irq_detach(RP2040_ADC_IRQ_FIFO); + + /* Flush the FIFO */ + + while (getreg32(RP2040_ADC_FCS) & RP2040_ADC_FCS_LEVEL_MASK) + { + getreg32(RP2040_ADC_FIFO); + } + + return OK; + } + + /* Fetch the data from the FIFO register */ + + value = getreg32(RP2040_ADC_FIFO); + error_bit_set = (value & RP2040_ADC_FIFO_ERR) != 0; + + /* Shift value to top of signed 32-bit word for upper-halfs benefit. */ + + value <<= 19; + + if (error_bit_set) + { + value = -value; + } + + for (a_device = g_first_device; + a_device != NULL; + a_device = PRIV(a_device)->next_device) + { + a_callback = PRIV(a_device)->callback; + + if (a_callback != NULL && a_callback->au_receive != NULL) + { + if (PRIV(a_device)->has_channel[g_current_channel]) + { + if (a_callback->au_receive(a_device, + g_current_channel, + value) != OK) + { + /* ### TODO ### Upper half buffer overflow */ + } + } + } + } + + /* Start next channel read */ + + get_next_channel(); + + return OK; +} + +/**************************************************************************** + * Name: get_next_channel + * + * Description: + * Update g_current_channel to point to next channel in use and start + * the conversion. + * + * Note: This is called from inside a critical section. + ****************************************************************************/ + +static void get_next_channel(void) +{ + FAR struct adc_dev_s *a_device; + uint8_t next = g_current_channel + 1; + uint32_t value; + + while (true) + { + if (next >= ADC_CHANNEL_COUNT) + { + next = 0; + } + + for (a_device = g_first_device; + a_device != NULL; + a_device = PRIV(a_device)->next_device) + { + if (PRIV(a_device)->has_channel[next]) + { + g_current_channel = next; + + while (getreg32(RP2040_ADC_FCS) + & RP2040_ADC_FCS_LEVEL_MASK) + { + getreg32(RP2040_ADC_FIFO); + } + + /* Enable Interrupt on ADC Completion */ + + putreg32(RP2040_ADC_INTE_FIFO, RP2040_ADC_INTE); + + /* Configure CS to read one value from current channel */ + + value = (g_current_channel << RP2040_ADC_CS_AINSEL_SHIFT) + | RP2040_ADC_CS_EN; + + if (g_current_channel == ADC_TEMP_CHANNEL) + { + value |= RP2040_ADC_CS_TS_ENA; + } + + putreg32(value, RP2040_ADC_CS); + + while ((getreg32(RP2040_ADC_CS) + & RP2040_ADC_CS_READY) == 0) + { + /* Wait for ready to go high. The rp2040 docs + * say this is only a few clock cycles so we'll + * just busy wait. + */ + } + + /* Start the conversion */ + + value += RP2040_ADC_CS_START_ONCE; + + putreg32(value, RP2040_ADC_CS); + + return; + } + } + + /* if we've looped all the way we're in trouble */ + + ASSERT(next != g_current_channel); + + /* try another */ + + next += 1; + } + + return; +} + +/**************************************************************************** + * Name: add_device + * + * Description: + * This function is called to link the device int the device list. + * It also makes sure ADC reads are taking place + * + * Note: This is called from inside a critical section. + ****************************************************************************/ + +static void add_device(FAR struct adc_dev_s *dev) +{ + uint32_t value; + + g_active_count += 1; + + if (g_first_device != NULL) + { + PRIV(g_first_device)->prior_device = dev; + } + + PRIV(dev)->next_device = g_first_device; + PRIV(dev)->prior_device = NULL; + + g_first_device = dev; + + if (PRIV(g_first_device)->next_device == NULL) + { + /* We just added first device */ + + /* Make sure ADC interrupts are disabled */ + + putreg32(0, RP2040_ADC_INTE); + + /* Configure FCS to use FIFO and interrupt on first value */ + + value = (1 << RP2040_ADC_FCS_THRESH_SHIFT) + | RP2040_ADC_FCS_OVER + | RP2040_ADC_FCS_UNDER + | RP2040_ADC_FCS_ERR + | RP2040_ADC_FCS_EN; + + putreg32(value, RP2040_ADC_FCS); + + /* Set up for interrupts */ + + irq_attach(RP2040_ADC_IRQ_FIFO, interrupt_handler, NULL); + + up_enable_irq(RP2040_ADC_IRQ_FIFO); + + /* Start conversions on first required channel. */ + + get_next_channel(); + + ainfo("new cur %d\n", g_current_channel); + } +} + +/**************************************************************************** + * Name: remove_device + * + * Description: + * This function is called to unlink the device from the device list. + * + * Note: This is called from inside a critical section. + ****************************************************************************/ + +void remove_device(FAR struct adc_dev_s *dev) +{ + FAR struct adc_dev_s *a_device; + + if (dev == g_first_device) + { + /* Special handling for first device */ + + g_first_device = PRIV(g_first_device)->next_device; + + if (g_first_device != NULL) + { + PRIV(g_first_device)->prior_device = NULL; + } + + g_active_count -= 1; + } + else + { + /* Make sure dev is on the change, and unlink if it is. */ + + for (a_device = g_first_device; + a_device != NULL; + a_device = PRIV(a_device)->next_device) + { + if (a_device == dev) + { + PRIV(PRIV(dev)->prior_device)->next_device = + PRIV(dev)->next_device; + + PRIV(PRIV(dev)->next_device)->prior_device = + PRIV(dev)->prior_device; + + g_active_count -= 1; + + break; + } + } + } +} + +/**************************************************************************** + * Name: my_bind + * + * Description: + * This function is called when a driver is registered. It give us a + * chance to bind the upper-half callbacks to our private data structure so + * they can be accessed later. + * + ****************************************************************************/ + +static int my_bind(struct adc_dev_s *dev, + const struct adc_callback_s *callback) +{ + DEBUGASSERT(PRIV(dev) != NULL); + + ainfo("entered\n"); + + PRIV(dev)->callback = callback; + + return OK; +} + +/**************************************************************************** + * Name: my_reset + * + * Description: + * This is called by the upper-half as part of the driver registration + * process. The upper half documentation also claims that it may + * be called as part of an error condition. + * + * Set the pin for the ADC's to standard GPIO input with no pulls. + * + ****************************************************************************/ + +static void my_reset(struct adc_dev_s *dev) +{ + int a_gpio; + + ainfo("entered\n"); + + for (int i = 0; i < ADC_CHANNEL_COUNT; ++i) + { + a_gpio = g_gpio_map[i]; + + if (a_gpio >= 0) + { + rp2040_gpio_setdir(a_gpio, false); + rp2040_gpio_set_function(a_gpio, RP2040_GPIO_FUNC_NULL); + rp2040_gpio_set_pulls(a_gpio, false, false); + } + } +} + +/**************************************************************************** + * Name: my_setup + * + * Description: + * This is called when a particular ADC driver is first opened. + * + * We don't do anything here. + * + * Note: This is called from inside a critical section. + ****************************************************************************/ + +static int my_setup(struct adc_dev_s *dev) +{ + int ret; + + ainfo("entered: 0x%08lX\n", dev); + + /* Note: We check g_active_count here so we can return an error + * in the, probably impossible, case we have too many. + */ + + if (g_active_count >= 200) + { + aerr("Too many active devices."); + ret = -EBUSY; + } + else + { + ret = OK; + } + + return ret; +} + +/**************************************************************************** + * Name: my_shutdown + * + * Description: + * This is called to shutdown an ADC device. It unlinks the + * device from out local chain and turns off ADC interrupts if no + * more devices are active. + * + * Note: This is called from inside a critical section. + ****************************************************************************/ + +static void my_shutdown(FAR struct adc_dev_s *dev) +{ + ainfo("entered: 0x%08lX\n", dev); + + /* Remove adc_dev_s structure from the list */ + + remove_device(dev); +} + +/**************************************************************************** + * Name: my_rxint + * + * Description: + * Call to enable or disable ADC RX interrupts + * + * Note: This is called from inside a critical section. + ****************************************************************************/ + +static void my_rxint(struct adc_dev_s *dev, bool enable) +{ + if (enable) + { + ainfo("entered: enable: 0x%08lX\n", dev); + + add_device(dev); + } + else + { + ainfo("entered: disable: 0x%08lX\n", dev); + + remove_device(dev); + } +} + +/**************************************************************************** + * Name: my_ioctl + * + * Description: + * All ioctl calls will be routed through this method + * + ****************************************************************************/ + +static int my_ioctl(struct adc_dev_s *dev, + int cmd, + unsigned long arg) +{ + /* No ioctl commands supported */ + + ainfo("entered\n"); + + return -ENOTTY; +} + +/**************************************************************************** + * Public Function + ****************************************************************************/ + +#ifdef CONFIG_ADC + +/**************************************************************************** + * Name: my_setup + * + * Description: + * Initialize and register the ADC driver. + * + * Input Parameters: + * path - Path to the ws2812 device (e.g. "/dev/adc0") + * read_adc0 - This device reads ADC0 + * read_adc1 - This device reads ADC1 + * read_adc2 - This device reads ADC3 + * read_adc3 - This device reads ADC4 + * read_temp - This device reads the chip temperature. + * + * Returned Value: + * An opaque pointer that can be passed to rp2040_adc_release on + * success or NULL (with errno set) on failure + ****************************************************************************/ + +int rp2040_adc_setup(FAR const char *path, + bool read_adc0, + bool read_adc1, + bool read_adc2, + bool read_adc3, + bool read_temp) +{ + FAR struct adc_dev_s *dev; + FAR struct private_s *priv; + int ret; + + ainfo("entered\n"); + + if (!read_adc0 && !read_adc1 && !read_adc2 && !read_adc3 && !read_temp) + { + aerr("No ADC inputs selected.\n"); + return -EINVAL; + } + + dev = kmm_zalloc(sizeof(struct adc_dev_s)); + + if (dev == NULL) + { + aerr("Failed to allocate adc_dev_s.\n"); + return -ENOMEM; + } + + priv = kmm_zalloc(sizeof(struct private_s)); + + if (priv == NULL) + { + aerr("Failed to allocate private_s.\n"); + kmm_free(dev); + return -ENOMEM; + } + + priv->has_channel[0] = read_adc0; + priv->has_channel[1] = read_adc1; + priv->has_channel[2] = read_adc2; + priv->has_channel[3] = read_adc3; + priv->has_channel[4] = read_temp; + + dev->ad_ops = &g_adcops; + dev->ad_priv = priv; + + ret = adc_register(path, dev); + + return ret; +} + +#endif /* if CONFIG_ADC */ +#endif /* if CONFIG_RP2040_ADC */ \ No newline at end of file diff --git a/arch/arm/src/rp2040/rp2040_adc.h b/arch/arm/src/rp2040/rp2040_adc.h new file mode 100644 index 0000000000..10aaf82e28 --- /dev/null +++ b/arch/arm/src/rp2040/rp2040_adc.h @@ -0,0 +1,108 @@ +/**************************************************************************** + * arch/arm/src/rp2040/rp2040_adc.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Note: + * The ADC driver upper-half returns signed values of up to 32-bits to + * the user and expects the high-order bits in any result to be + * significant. So, while the RP2040 hardware returns an unsigned value + * in the low-order 12 bits of the result register, we shift this + * value to the high-order bits. + * + * The result is that to convert a 32-bit value returned from the ADC + * driver, you should use V = ADC_AVDD * value / (2^31) where ADC_AVDD + * is the analogue reference voltage supplied to the RP2040 chip. If + * 8 or 16 bit values are returned the divisor would be (2^15) or (2^7) + * respectively. + * + * Also, if the conversion error bit was set for a particular sample, + * the return value will be negated. Any negative return value should + * be treated as erroneous. + ****************************************************************************/ + +#ifndef __ARCH_ARM_SRC_RP2040_RP2040_ADC_H +#define __ARCH_ARM_SRC_RP2040_RP2040_ADC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifndef __ASSEMBLY__ +#if defined(__cplusplus) +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +#ifdef CONFIG_RP2040_ADC + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +#ifdef CONFIG_ADC + +/**************************************************************************** + * Name: rp2040_adc_setup + * + * Description: + * Initialize and register the ADC driver. + * + * Input Parameters: + * path - Path to the ws2812 device (e.g. "/dev/adc0") + * read_adc0 - This device reads ADC0 + * read_adc1 - This device reads ADC1 + * read_adc2 - This device reads ADC3 + * read_adc3 - This device reads ADC4 + * read_temp - This device reads the chip temperature. + * + * Returned Value: + * OK on success or an ERROR on failure + ****************************************************************************/ + +int rp2040_adc_setup(FAR const char *path, + bool read_adc0, + bool read_adc1, + bool read_adc2, + bool read_adc3, + bool read_temp); + +#else /* CONFIG_ADC */ + +/* ### TODO ### Add programmatic access function. */ + +#endif /* CONFIG_ADC */ + +#endif /* CONFIG_RP2040_ADC */ + +#undef EXTERN +#if defined(__cplusplus) +} +#endif + +#endif /* __ASSEMBLY__ */ + +#endif /* __ARCH_ARM_SRC_RP2040_RP2040_ADC_H */ diff --git a/arch/arm/src/rp2040/rp2040_ws2812.h b/arch/arm/src/rp2040/rp2040_ws2812.h index 533eb0dc18..3f91cfc9fe 100644 --- a/arch/arm/src/rp2040/rp2040_ws2812.h +++ b/arch/arm/src/rp2040/rp2040_ws2812.h @@ -92,4 +92,4 @@ int rp2040_ws2812_release(FAR void * driver); #endif #endif /* __ASSEMBLY__ */ -#endif /* __ARCH_ARM_SRC_RP2040_RP2040_I2C_H */ +#endif /* __ARCH_ARM_SRC_RP2040_RP2040_WS2812_H */ diff --git a/boards/Kconfig b/boards/Kconfig index c71cb1b3a1..0cb6e5fed4 100644 --- a/boards/Kconfig +++ b/boards/Kconfig @@ -3510,6 +3510,9 @@ endif if ARCH_CHIP_STM32 source "boards/arm/stm32/common/Kconfig" endif +if ARCH_CHIP_RP2040 +source "boards/arm/rp2040/common/Kconfig" +endif endif config BOARD_CRASHDUMP diff --git a/boards/arm/rp2040/adafruit-feather-rp2040/README.txt b/boards/arm/rp2040/adafruit-feather-rp2040/README.txt index 0ff8384310..c289a627f2 100644 --- a/boards/arm/rp2040/adafruit-feather-rp2040/README.txt +++ b/boards/arm/rp2040/adafruit-feather-rp2040/README.txt @@ -14,6 +14,7 @@ Currently only the following devices are supported. - SPI - DMAC - PWM + - ADC - USB device - MSC, CDC/ACM serial and these composite device are supported. - CDC/ACM serial device can be used for the console. diff --git a/boards/arm/rp2040/adafruit-feather-rp2040/src/rp2040_bringup.c b/boards/arm/rp2040/adafruit-feather-rp2040/src/rp2040_bringup.c index f564f478fd..c0637c8131 100644 --- a/boards/arm/rp2040/adafruit-feather-rp2040/src/rp2040_bringup.c +++ b/boards/arm/rp2040/adafruit-feather-rp2040/src/rp2040_bringup.c @@ -74,6 +74,10 @@ #define HAS_WHITE false #endif /* CONFIG_WS2812_HAS_WHITE */ +#if defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) +#include "rp2040_adc.h" +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -462,5 +466,46 @@ int rp2040_bringup(void) HAS_WHITE); #endif +#if defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) + +# ifdef CONFIG_RPC2040_ADC_CHANNEL0 +# define ADC_0 true +# else +# define ADC_0 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL1 +# define ADC_1 true +# else +# define ADC_1 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL2 +# define ADC_2 true +# else +# define ADC_2 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL3 +# define ADC_3 true +# else +# define ADC_3 false +# endif + +# ifdef CONFIG_RPC2040_ADC_TEMPERATURE +# define ADC_TEMP true +# else +# define ADC_TEMP false +# endif + + ret = rp2040_adc_setup("/dev/adc0", ADC_0, ADC_1, ADC_2, ADC_3, ADC_TEMP); + if (ret != OK) + { + syslog(LOG_ERR, "Failed to initialize ADC Driver: %d\n", ret); + return ret; + } + +#endif /* defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) */ + return ret; } diff --git a/boards/arm/rp2040/adafruit-kb2040/README.txt b/boards/arm/rp2040/adafruit-kb2040/README.txt index 3bce577003..7451e2cfd3 100644 --- a/boards/arm/rp2040/adafruit-kb2040/README.txt +++ b/boards/arm/rp2040/adafruit-kb2040/README.txt @@ -13,6 +13,7 @@ Currently only the following devices are supported. - SPI - DMAC - PWM + - ADC - USB device - MSC, CDC/ACM serial and these composite device are supported. - CDC/ACM serial device can be used for the console. diff --git a/boards/arm/rp2040/adafruit-kb2040/src/rp2040_bringup.c b/boards/arm/rp2040/adafruit-kb2040/src/rp2040_bringup.c index 2ea4c442ff..c1a071ccfe 100644 --- a/boards/arm/rp2040/adafruit-kb2040/src/rp2040_bringup.c +++ b/boards/arm/rp2040/adafruit-kb2040/src/rp2040_bringup.c @@ -74,6 +74,10 @@ #define HAS_WHITE false #endif /* CONFIG_WS2812_HAS_WHITE */ +#if defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) +#include "rp2040_adc.h" +#endif + /**************************************************************************** * Public Functions ****************************************************************************/ @@ -462,5 +466,46 @@ int rp2040_bringup(void) HAS_WHITE); #endif +#if defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) + +# ifdef CONFIG_RPC2040_ADC_CHANNEL0 +# define ADC_0 true +# else +# define ADC_0 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL1 +# define ADC_1 true +# else +# define ADC_1 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL2 +# define ADC_2 true +# else +# define ADC_2 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL3 +# define ADC_3 true +# else +# define ADC_3 false +# endif + +# ifdef CONFIG_RPC2040_ADC_TEMPERATURE +# define ADC_TEMP true +# else +# define ADC_TEMP false +# endif + + ret = rp2040_adc_setup("/dev/adc0", ADC_0, ADC_1, ADC_2, ADC_3, ADC_TEMP); + if (ret != OK) + { + syslog(LOG_ERR, "Failed to initialize ADC Driver: %d\n", ret); + return ret; + } + +#endif /* defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) */ + return ret; } diff --git a/boards/arm/rp2040/common/Kconfig b/boards/arm/rp2040/common/Kconfig new file mode 100644 index 0000000000..266bebabdd --- /dev/null +++ b/boards/arm/rp2040/common/Kconfig @@ -0,0 +1,54 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +if ARCH_BOARD_RASPBERRYPI_PICO + +config RP2040_ADC + bool "Enable ADC Support" + default n + ---help--- + If y, the RP2040 ADC code will be built. For ADC device driver + support you must also select "Analog-to-Digital Conversion" + under the "Device Driver/Analog Device Support" menu. + + If the ADC device driver is not built, basic functions + to programatically access the ADC ports will be added. + +if RP2040_ADC && ADC + + config RPC2040_ADC_CHANNEL0 + bool "Read ADC channel 0" + default n + ---help--- + If y, then ADC0 will be read. + + config RPC2040_ADC_CHANNEL1 + bool "Read ADC channel 1" + default n + ---help--- + If y, then ADC1 will be read. + + config RPC2040_ADC_CHANNEL2 + bool "Read ADC channel 2" + default n + ---help--- + If y, then ADC2 will be read. + + config RPC2040_ADC_CHANNEL3 + bool "Read ADC channel 3" + default n + ---help--- + If y, then ADC3 will be read. + + config RPC2040_ADC_TEMPERATURE + bool "Read ADC chip temperature channel" + default n + ---help--- + If y, then the ADC chip temperature + will be read. + + endif + +endif diff --git a/boards/arm/rp2040/pimoroni-tiny2040/README.txt b/boards/arm/rp2040/pimoroni-tiny2040/README.txt index c742f8c037..12af598c3d 100644 --- a/boards/arm/rp2040/pimoroni-tiny2040/README.txt +++ b/boards/arm/rp2040/pimoroni-tiny2040/README.txt @@ -17,6 +17,7 @@ Currently only the following devices are supported. - SPI - DMAC - PWM + - ADC - USB device - MSC, CDC/ACM serial and these composite device are supported. - CDC/ACM serial device can be used for the console. diff --git a/boards/arm/rp2040/pimoroni-tiny2040/src/rp2040_bringup.c b/boards/arm/rp2040/pimoroni-tiny2040/src/rp2040_bringup.c index cfeb9cc61e..8082bbf121 100644 --- a/boards/arm/rp2040/pimoroni-tiny2040/src/rp2040_bringup.c +++ b/boards/arm/rp2040/pimoroni-tiny2040/src/rp2040_bringup.c @@ -38,6 +38,10 @@ #include "rp2040_pwmdev.h" #endif +#if defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) +#include "rp2040_adc.h" +#endif + #if defined(CONFIG_RP2040_BOARD_HAS_WS2812) && defined(CONFIG_WS2812) #include "rp2040_ws2812.h" #endif @@ -408,6 +412,49 @@ int rp2040_bringup(void) # endif #endif + /* Initialize ADC */ + +#if defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) + +# ifdef CONFIG_RPC2040_ADC_CHANNEL0 +# define ADC_0 true +# else +# define ADC_0 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL1 +# define ADC_1 true +# else +# define ADC_1 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL2 +# define ADC_2 true +# else +# define ADC_2 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL3 +# define ADC_3 true +# else +# define ADC_3 false +# endif + +# ifdef CONFIG_RPC2040_ADC_TEMPERATURE +# define ADC_TEMP true +# else +# define ADC_TEMP false +# endif + + ret = rp2040_adc_setup("/dev/adc0", ADC_0, ADC_1, ADC_2, ADC_3, ADC_TEMP); + if (ret != OK) + { + syslog(LOG_ERR, "Failed to initialize ADC Driver: %d\n", ret); + return ret; + } + +#endif /* defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) */ + /* Initialize board neo-pixel */ #if defined(CONFIG_RP2040_BOARD_HAS_WS2812) && defined(CONFIG_WS2812) diff --git a/boards/arm/rp2040/raspberrypi-pico/README.txt b/boards/arm/rp2040/raspberrypi-pico/README.txt index ed799eadd3..4bd892f45c 100644 --- a/boards/arm/rp2040/raspberrypi-pico/README.txt +++ b/boards/arm/rp2040/raspberrypi-pico/README.txt @@ -14,6 +14,7 @@ Currently only the following devices are supported. - SPI - DMAC - PWM + - ADC - USB device - MSC, CDC/ACM serial and these composite device are supported. - CDC/ACM serial device can be used for the console. diff --git a/boards/arm/rp2040/raspberrypi-pico/src/rp2040_bringup.c b/boards/arm/rp2040/raspberrypi-pico/src/rp2040_bringup.c index 23face61e8..b5b19e7e1b 100644 --- a/boards/arm/rp2040/raspberrypi-pico/src/rp2040_bringup.c +++ b/boards/arm/rp2040/raspberrypi-pico/src/rp2040_bringup.c @@ -64,6 +64,10 @@ #include "rp2040_pwmdev.h" #endif +#if defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) +#include "rp2040_adc.h" +#endif + #if defined(CONFIG_RP2040_BOARD_HAS_WS2812) && defined(CONFIG_WS2812) #include "rp2040_ws2812.h" #endif @@ -487,6 +491,49 @@ int rp2040_bringup(void) } #endif + /* Initialize ADC */ + +#if defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) + +# ifdef CONFIG_RPC2040_ADC_CHANNEL0 +# define ADC_0 true +# else +# define ADC_0 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL1 +# define ADC_1 true +# else +# define ADC_1 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL2 +# define ADC_2 true +# else +# define ADC_2 false +# endif + +# ifdef CONFIG_RPC2040_ADC_CHANNEL3 +# define ADC_3 true +# else +# define ADC_3 false +# endif + +# ifdef CONFIG_RPC2040_ADC_TEMPERATURE +# define ADC_TEMP true +# else +# define ADC_TEMP false +# endif + + ret = rp2040_adc_setup("/dev/adc0", ADC_0, ADC_1, ADC_2, ADC_3, ADC_TEMP); + if (ret != OK) + { + syslog(LOG_ERR, "Failed to initialize ADC Driver: %d\n", ret); + return ret; + } + +#endif /* defined(CONFIG_ADC) && defined(CONFIG_RP2040_ADC) */ + /* Initialize board neo-pixel */ #if defined(CONFIG_RP2040_BOARD_HAS_WS2812) && defined(CONFIG_WS2812) diff --git a/drivers/analog/adc.c b/drivers/analog/adc.c index a27866d1a5..02b2f37d69 100644 --- a/drivers/analog/adc.c +++ b/drivers/analog/adc.c @@ -148,14 +148,14 @@ static int adc_open(FAR struct file *filep) /* Finally, Enable the ADC RX interrupt */ dev->ad_ops->ao_rxint(dev, true); - - /* Save the new open count on success */ - - dev->ad_ocount = tmp; } leave_critical_section(flags); } + + /* Save the new open count on success */ + + dev->ad_ocount = tmp; } nxsem_post(&dev->ad_closesem);