pw_i2c_rp2040 implementation

Change-Id: I44affcb9dd35bca8843b6378008f1fc5506047a7
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/experimental/+/171594
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Armando Montanez <amontanez@google.com>
Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
diff --git a/applications/app_common_impl/BUILD.gn b/applications/app_common_impl/BUILD.gn
index d84a014..df20efb 100644
--- a/applications/app_common_impl/BUILD.gn
+++ b/applications/app_common_impl/BUILD.gn
@@ -136,6 +136,7 @@
   "$dir_pw_digital_io_pico",
   "$dir_pw_display",
   "$dir_pw_framebuffer_pool",
+  "$dir_pw_i2c_rp2040",
   "$dir_pw_log",
   "$dir_pw_spi_pico",
   "$dir_pw_sync:borrow",
diff --git a/applications/app_common_impl/common_pico.cc b/applications/app_common_impl/common_pico.cc
index c2c2cc5..8abc503 100644
--- a/applications/app_common_impl/common_pico.cc
+++ b/applications/app_common_impl/common_pico.cc
@@ -23,10 +23,12 @@
 #include "hardware/vreg.h"
 #include "pico/stdlib.h"
 #include "pw_digital_io_pico/digital_io.h"
+#include "pw_i2c_rp2040/initiator.h"
 #include "pw_log/log.h"
 #include "pw_pixel_pusher_rp2040_pio/pixel_pusher.h"
 #include "pw_spi_pico/chip_selector.h"
 #include "pw_spi_pico/initiator.h"
+#include "pw_status/status.h"
 #include "pw_sync/borrow.h"
 #include "pw_sync/mutex.h"
 
@@ -178,6 +180,15 @@
       borrowable_initiator(initiator, initiator_mutex),
       device(borrowable_initiator, config, selector) {}
 
+constexpr pw::i2c::PicoInitiator::Config ki2cConfig{
+    .i2c_block = 0,
+    .baud_rate_bps = 400'000,
+    .sda_pin = 4,
+    .scl_pin = 5,
+};
+
+pw::i2c::PicoInitiator i2c_bus(ki2cConfig);
+
 }  // namespace
 
 // static
@@ -206,6 +217,8 @@
   s_display_tear_effect_pin.Enable();
 #endif
 
+  i2c_bus.Enable();
+
 #if BACKLIGHT_GPIO != -1
   SetBacklight(0xffff);  // Full brightness.
 #endif
diff --git a/build_overrides/pigweed.gni b/build_overrides/pigweed.gni
index cce6d5c..210f6f9 100644
--- a/build_overrides/pigweed.gni
+++ b/build_overrides/pigweed.gni
@@ -104,6 +104,8 @@
                     "abspath")
   dir_pw_spi_arduino =
       get_path_info("$dir_pigweed_experimental/pw_spi_arduino", "abspath")
+  dir_pw_i2c_rp2040 =
+      get_path_info("$dir_pigweed_experimental/pw_i2c_rp2040", "abspath")
   dir_pw_spi_pico =
       get_path_info("$dir_pigweed_experimental/pw_spi_pico", "abspath")
   dir_pw_spi_stm32cube =
diff --git a/pw_i2c_rp2040/BUILD.gn b/pw_i2c_rp2040/BUILD.gn
new file mode 100644
index 0000000..7ddbca1
--- /dev/null
+++ b/pw_i2c_rp2040/BUILD.gn
@@ -0,0 +1,42 @@
+# Copyright 2023 The Pigweed Authors
+#
+# Licensed 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
+#
+#     https://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.
+import("//build_overrides/pi_pico.gni")
+import("//build_overrides/pigweed.gni")
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+  include_dirs = [ "public" ]
+}
+
+pw_source_set("pw_i2c_rp2040") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_i2c_rp2040/initiator.h" ]
+  public_deps = [
+    "$PICO_ROOT/src/rp2_common/hardware_i2c",
+    "$dir_pw_i2c:initiator",
+    "$dir_pw_status",
+  ]
+  deps = [
+    "$PICO_ROOT/src/common/pico_base",
+    "$PICO_ROOT/src/common/pico_stdlib",
+    "$dir_pw_digital_io",
+    "$dir_pw_log",
+    "$dir_pw_sync:interrupt_spin_lock",
+    "$dir_pw_sync:lock_annotations",
+    "$dir_pw_sync:mutex",
+    "$dir_pw_sync:timed_thread_notification",
+  ]
+  sources = [ "initiator.cc" ]
+  remove_configs = [ "$dir_pw_build:strict_warnings" ]
+}
diff --git a/pw_i2c_rp2040/initiator.cc b/pw_i2c_rp2040/initiator.cc
new file mode 100644
index 0000000..30946aa
--- /dev/null
+++ b/pw_i2c_rp2040/initiator.cc
@@ -0,0 +1,139 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+#include "pw_i2c_rp2040/initiator.h"
+
+#include <chrono>
+#include <mutex>
+
+#include "hardware/gpio.h"
+#include "hardware/i2c.h"
+#include "pico/error.h"
+#include "pw_chrono/system_clock.h"
+#include "pw_status/status.h"
+#include "pw_status/try.h"
+
+namespace pw::i2c {
+
+namespace {
+
+Status PicoStatusToPwStatus(int status) {
+  if (status > 0)
+    return OkStatus();
+
+  switch (status) {
+    case PICO_ERROR_TIMEOUT:
+      return Status::DeadlineExceeded();
+    default:
+      return Status::Unavailable();
+  }
+}
+}  // namespace
+
+void PicoInitiator::Enable() {
+  std::lock_guard lock(mutex_);
+
+  if (config_.i2c_block == 0) {
+    base_ = i2c0;
+  } else {
+    base_ = i2c1;
+  }
+  i2c_init(base_, config_.baud_rate_bps);
+  gpio_set_function(config_.sda_pin, GPIO_FUNC_I2C);
+  gpio_set_function(config_.scl_pin, GPIO_FUNC_I2C);
+
+  enabled_ = true;
+}
+
+void PicoInitiator::Disable() {
+  std::lock_guard lock(mutex_);
+  i2c_deinit(base_);
+  enabled_ = false;
+}
+
+PicoInitiator::~PicoInitiator() { Disable(); }
+
+// Performs non-blocking I2C write, read and read-after-write depending on the
+// tx and rx buffer states.
+Status PicoInitiator::DoWriteReadFor(Address device_address,
+                                     ConstByteSpan tx_buffer,
+                                     ByteSpan rx_buffer,
+                                     chrono::SystemClock::duration timeout) {
+  if (timeout <= chrono::SystemClock::duration::zero()) {
+    return Status::DeadlineExceeded();
+  }
+  const int64_t timeout_us = std::chrono::microseconds(timeout).count();
+
+  if (timeout_us > std::numeric_limits<uint>::max()) {
+    return Status::InvalidArgument();
+  }
+
+  const uint8_t address = device_address.GetSevenBit();
+  std::lock_guard lock(mutex_);
+
+  if (!enabled_) {
+    return Status::FailedPrecondition();
+  }
+
+  if (!tx_buffer.empty() && rx_buffer.empty()) {
+    // Write
+    int result = i2c_write_timeout_us(
+        base_,
+        address,
+        /*src=*/reinterpret_cast<const uint8_t*>(tx_buffer.data()),
+        /*len=*/tx_buffer.size(),
+        /*nostop=*/false,
+        static_cast<uint>(timeout_us));
+
+    return PicoStatusToPwStatus(result);
+
+  } else if (tx_buffer.empty() && !rx_buffer.empty()) {
+    // Read
+    int result = i2c_read_timeout_us(
+        base_,
+        address,
+        /*src=*/reinterpret_cast<uint8_t*>(rx_buffer.data()),
+        /*len=*/rx_buffer.size(),
+        /*nostop=*/false,
+        static_cast<uint>(timeout_us));
+    return PicoStatusToPwStatus(result);
+
+  } else if (!tx_buffer.empty() && !rx_buffer.empty()) {
+    // Write then Read
+    pw::Status write_result(PicoStatusToPwStatus(i2c_write_timeout_us(
+        base_,
+        address,
+        /*src=*/reinterpret_cast<const uint8_t*>(tx_buffer.data()),
+        /*len=*/tx_buffer.size(),
+        /*nostop=*/true,
+        static_cast<uint>(timeout_us))));
+
+    if (write_result != OkStatus()) {
+      return write_result;
+    }
+
+    int read_result = i2c_read_timeout_us(
+        base_,
+        address,
+        /*src=*/reinterpret_cast<uint8_t*>(rx_buffer.data()),
+        /*len=*/rx_buffer.size(),
+        /*nostop=*/false,
+        static_cast<uint>(timeout_us));
+
+    return PicoStatusToPwStatus(read_result);
+
+  } else {
+    return Status::InvalidArgument();
+  }
+}
+}  // namespace pw::i2c
diff --git a/pw_i2c_rp2040/public/pw_i2c_rp2040/initiator.h b/pw_i2c_rp2040/public/pw_i2c_rp2040/initiator.h
new file mode 100644
index 0000000..5438c10
--- /dev/null
+++ b/pw_i2c_rp2040/public/pw_i2c_rp2040/initiator.h
@@ -0,0 +1,59 @@
+// Copyright 2022 The Pigweed Authors
+//
+// Licensed 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
+//
+//     https://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.
+#pragma once
+
+#include <cstdint>
+
+#include "hardware/i2c.h"
+#include "pw_i2c/initiator.h"
+#include "pw_sync/interrupt_spin_lock.h"
+#include "pw_sync/lock_annotations.h"
+#include "pw_sync/mutex.h"
+
+namespace pw::i2c {
+
+// Initiator interface implementation based on I2C driver in Raspberry Pi Pico
+// SDK.  Currently supports only devices with 7 bit adresses.
+class PicoInitiator final : public Initiator {
+ public:
+  struct Config {
+    uint32_t i2c_block;  // 0 or 1
+    uint32_t baud_rate_bps;
+    uint8_t sda_pin;
+    uint8_t scl_pin;
+  };
+
+  PicoInitiator(const Config& config) : config_(config) {}
+
+  // Should be called before attempting any transfers.
+  void Enable() PW_LOCKS_EXCLUDED(mutex_);
+  void Disable() PW_LOCKS_EXCLUDED(mutex_);
+
+  ~PicoInitiator() final;
+
+ private:
+  Status DoWriteReadFor(Address device_address,
+                        ConstByteSpan tx_buffer,
+                        ByteSpan rx_buffer,
+                        chrono::SystemClock::duration timeout) override
+      PW_LOCKS_EXCLUDED(mutex_);
+
+ private:
+  sync::Mutex mutex_;
+  Config const config_;
+  i2c_inst_t* base_;
+  bool enabled_ PW_GUARDED_BY(mutex_);
+};
+
+}  // namespace pw::i2c
diff --git a/targets/rp2040/BUILD.gn b/targets/rp2040/BUILD.gn
index 10f7072..9069efc 100644
--- a/targets/rp2040/BUILD.gn
+++ b/targets/rp2040/BUILD.gn
@@ -82,6 +82,10 @@
     pw_sync_MUTEX_BACKEND = "$dir_pw_sync_baremetal:mutex"
     pw_sync_COUNTING_SEMAPHORE_BACKEND =
         "$dir_pw_sync_freertos:counting_semaphore"
+    pw_sync_THREAD_NOTIFICATION_BACKEND =
+        "$dir_pw_sync_freertos:thread_notification"
+    pw_sync_TIMED_THREAD_NOTIFICATION_BACKEND =
+        "$dir_pw_sync_freertos:timed_thread_notification"
 
     pw_rpc_CONFIG = "$dir_pw_rpc:disable_global_mutex"