| // Copyright 2024 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 "pico/types.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 Rp2040Initiator::Enable() { |
| std::lock_guard lock(mutex_); |
| |
| i2c_init(instance_, config_.clock_frequency); |
| gpio_set_function(config_.sda_pin, GPIO_FUNC_I2C); |
| gpio_set_function(config_.scl_pin, GPIO_FUNC_I2C); |
| |
| enabled_ = true; |
| } |
| |
| void Rp2040Initiator::Disable() { |
| std::lock_guard lock(mutex_); |
| i2c_deinit(instance_); |
| enabled_ = false; |
| } |
| |
| Rp2040Initiator::~Rp2040Initiator() { Disable(); } |
| |
| // Performs blocking I2C write, read and read-after-write depending on the tx |
| // and rx buffer states. |
| Status Rp2040Initiator::DoWriteReadFor(Address device_address, |
| ConstByteSpan tx_buffer, |
| ByteSpan rx_buffer, |
| chrono::SystemClock::duration timeout) { |
| if (timeout <= chrono::SystemClock::duration::zero()) { |
| return Status::DeadlineExceeded(); |
| } |
| // The number of us to wait plus + 1 to ensure we wait at least the full |
| // duration. Ideally we would add one additional tick but the pico_sdk only |
| // supports timeouts specified in us. |
| const int64_t timeout_us = std::chrono::microseconds(timeout).count() + 1; |
| |
| 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( |
| instance_, |
| 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( |
| instance_, |
| 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()) { |
| absolute_time_t timeout_absolute = make_timeout_time_us(timeout_us); |
| // Write then Read |
| int write_result = i2c_write_blocking_until( |
| instance_, |
| address, |
| /*src=*/reinterpret_cast<const uint8_t*>(tx_buffer.data()), |
| /*len=*/tx_buffer.size(), |
| /*nostop=*/true, |
| /*until=*/timeout_absolute); |
| pw::Status write_status(PicoStatusToPwStatus(write_result)); |
| |
| if (write_status != OkStatus()) { |
| return write_status; |
| } |
| |
| int read_result = i2c_read_blocking_until( |
| instance_, |
| address, |
| /*src=*/reinterpret_cast<uint8_t*>(rx_buffer.data()), |
| /*len=*/rx_buffer.size(), |
| /*nostop=*/false, |
| /*until=*/timeout_absolute); |
| |
| return PicoStatusToPwStatus(read_result); |
| |
| } else { |
| return Status::InvalidArgument(); |
| } |
| } |
| } // namespace pw::i2c |