blob: 749d2a37ac5c92a19e7f85d8b1c10db181dd9fad [file] [log] [blame]
// 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