From 9944ca414e45c52f560349c420212a7cbb875bdb Mon Sep 17 00:00:00 2001
From: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Date: Thu, 17 Nov 2022 13:51:08 +1300
Subject: [PATCH] Support ADC on RP2040 (#4040)

---
 esphome/components/adc/adc_sensor.cpp | 51 +++++++++++++++++++++++++--
 esphome/components/adc/adc_sensor.h   |  8 +++++
 esphome/components/adc/sensor.py      | 11 ++++++
 esphome/config_validation.py          |  1 +
 4 files changed, 69 insertions(+), 2 deletions(-)

diff --git a/esphome/components/adc/adc_sensor.cpp b/esphome/components/adc/adc_sensor.cpp
index 5a29d86404..1e8d740062 100644
--- a/esphome/components/adc/adc_sensor.cpp
+++ b/esphome/components/adc/adc_sensor.cpp
@@ -11,6 +11,10 @@ ADC_MODE(ADC_VCC)
 #endif
 #endif
 
+#ifdef USE_RP2040
+#include <hardware/adc.h>
+#endif
+
 namespace esphome {
 namespace adc {
 
@@ -32,9 +36,13 @@ static const int ADC_MAX = (1 << SOC_ADC_RTC_MAX_BITWIDTH) - 1;    // 4095 (12 b
 static const int ADC_HALF = (1 << SOC_ADC_RTC_MAX_BITWIDTH) >> 1;  // 2048 (12 bit) or 4096 (13 bit)
 #endif
 
-void ADCSensor::setup() {
+#ifdef USE_RP2040
+extern "C"
+#endif
+    void
+    ADCSensor::setup() {
   ESP_LOGCONFIG(TAG, "Setting up ADC '%s'...", this->get_name().c_str());
-#ifndef USE_ADC_SENSOR_VCC
+#if !defined(USE_ADC_SENSOR_VCC) && !defined(USE_RP2040)
   pin_->setup();
 #endif
 
@@ -63,6 +71,16 @@ void ADCSensor::setup() {
   }
 
 #endif  // USE_ESP32
+
+#ifdef USE_RP2040
+  static bool initialized = false;
+  if (!initialized) {
+    adc_init();
+    initialized = true;
+  }
+#endif
+
+  ESP_LOGCONFIG(TAG, "ADC '%s' setup finished!", this->get_name().c_str());
 }
 
 void ADCSensor::dump_config() {
@@ -98,6 +116,12 @@ void ADCSensor::dump_config() {
     }
   }
 #endif  // USE_ESP32
+#ifdef USE_RP2040
+  if (this->is_temperature_)
+    ESP_LOGCONFIG(TAG, "  Pin: Temperature");
+  else
+    LOG_PIN("  Pin: ", pin_);
+#endif
   LOG_UPDATE_INTERVAL(this);
 }
 
@@ -175,6 +199,29 @@ float ADCSensor::sample() {
 }
 #endif  // USE_ESP32
 
+#ifdef USE_RP2040
+float ADCSensor::sample() {
+  if (this->is_temperature_) {
+    adc_set_temp_sensor_enabled(true);
+    delay(1);
+    adc_select_input(4);
+  } else {
+    uint8_t pin = this->pin_->get_pin();
+    adc_gpio_init(pin);
+    adc_select_input(pin - 26);
+  }
+
+  int raw = adc_read();
+  if (this->is_temperature_) {
+    adc_set_temp_sensor_enabled(false);
+  }
+  if (output_raw_) {
+    return raw;
+  }
+  return raw * 3.3f / 4096.0f;
+}
+#endif
+
 #ifdef USE_ESP8266
 std::string ADCSensor::unique_id() { return get_mac_address() + "-adc"; }
 #endif
diff --git a/esphome/components/adc/adc_sensor.h b/esphome/components/adc/adc_sensor.h
index 12272a1577..22cddde6f8 100644
--- a/esphome/components/adc/adc_sensor.h
+++ b/esphome/components/adc/adc_sensor.h
@@ -38,10 +38,18 @@ class ADCSensor : public sensor::Sensor, public PollingComponent, public voltage
   std::string unique_id() override;
 #endif
 
+#ifdef USE_RP2040
+  void set_is_temperature() { is_temperature_ = true; }
+#endif
+
  protected:
   InternalGPIOPin *pin_;
   bool output_raw_{false};
 
+#ifdef USE_RP2040
+  bool is_temperature_{false};
+#endif
+
 #ifdef USE_ESP32
   adc_atten_t attenuation_{ADC_ATTEN_DB_0};
   adc1_channel_t channel_{};
diff --git a/esphome/components/adc/sensor.py b/esphome/components/adc/sensor.py
index 5443b9875a..1a519d7506 100644
--- a/esphome/components/adc/sensor.py
+++ b/esphome/components/adc/sensor.py
@@ -94,6 +94,9 @@ def validate_adc_pin(value):
     if str(value).upper() == "VCC":
         return cv.only_on_esp8266("VCC")
 
+    if str(value).upper() == "TEMPERATURE":
+        return cv.only_on_rp2040("TEMPERATURE")
+
     if CORE.is_esp32:
         value = pins.internal_gpio_input_pin_number(value)
         variant = get_esp32_variant()
@@ -117,6 +120,12 @@ def validate_adc_pin(value):
             {CONF_ANALOG: True, CONF_INPUT: True}, internal=True
         )(value)
 
+    if CORE.is_rp2040:
+        value = pins.internal_gpio_input_pin_number(value)
+        if value not in (26, 27, 28, 29):
+            raise cv.Invalid("RP2040: Only pins 26, 27, 28 and 29 support ADC.")
+        return pins.internal_gpio_input_pin_schema(value)
+
     raise NotImplementedError
 
 
@@ -160,6 +169,8 @@ async def to_code(config):
 
     if config[CONF_PIN] == "VCC":
         cg.add_define("USE_ADC_SENSOR_VCC")
+    elif config[CONF_PIN] == "TEMPERATURE":
+        cg.add(var.set_is_temperature())
     else:
         pin = await cg.gpio_pin_expression(config[CONF_PIN])
         cg.add(var.set_pin(pin))
diff --git a/esphome/config_validation.py b/esphome/config_validation.py
index afc800f3f2..90018b4d56 100644
--- a/esphome/config_validation.py
+++ b/esphome/config_validation.py
@@ -548,6 +548,7 @@ def only_with_framework(frameworks):
 
 only_on_esp32 = only_on("esp32")
 only_on_esp8266 = only_on("esp8266")
+only_on_rp2040 = only_on("rp2040")
 only_with_arduino = only_with_framework("arduino")
 only_with_esp_idf = only_with_framework("esp-idf")