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"