blob: 7383c5da2235805a753c33c7d2aa2d6a83ca63de [file] [log] [blame]
// Copyright 2020 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 <cstddef>
#include "pw_assert/assert.h"
#include "pw_bytes/endianness.h"
#include "pw_i2c/i2c_bus.h"
#include "pw_preprocessor/util.h"
namespace pw::i2c {
// Represents an I2C slave register device.
// Instead of using a template class, command_type and register_type are taken
// as parameters during the construction time. By doing this, the object is
// evaluated in run-time instead of compile time
// enum class IntSize { kUint8, kUint16, kUint32, kUint64 };
// All values to read/write will be passed as pointer.
// Check demo_app_main.cc for sample usage
class I2cRegisterDevice {
public:
constexpr I2cRegisterDevice(I2cBus& i2c_bus,
I2cAddress i2c_addr,
pw::IntSize command_type,
pw::IntSize register_type,
pw::ByteOrder kEndianness)
: command_type(command_type),
register_type(register_type),
kEndianness(kEndianness),
i2c_bus_(i2c_bus),
i2c_addr_(i2c_addr) {}
// Write the command to the I2C bus, then read rx_buffer.size() bytes from
// the bus.
pw::Status Read(void* command, std::span<std::byte> rx_buffer) {
const size_t command_size = pw::GetIntSize(command_type);
std::byte command_buffer[command_size];
pw::ConvertToEndian(command,
ByteSpan(command_buffer, command_size),
command_type,
kEndianness);
return i2c_bus_.WriteRead(
i2c_addr_, std::span(command_buffer, command_size), rx_buffer);
}
// Read the value in register_id into rx_buffer
pw::Status ReadRegister(void* register_id, void* rx_buffer) {
const size_t register_size = pw::GetIntSize(register_type);
std::byte return_buffer[register_size];
pw::Status read_status =
Read(register_id, std::span(return_buffer, register_size));
if (!read_status.ok()) {
return read_status;
}
pw::GetEndian(ConstByteSpan(return_buffer, register_size),
rx_buffer,
register_type,
kEndianness);
return pw::Status::OK;
}
// Write tx_buffer to the I2C bus.
pw::Status Write(std::span<const std::byte> tx_buffer) {
return i2c_bus_.Write(i2c_addr_, tx_buffer);
}
// Write the value to register_id
pw::Status WriteRegister(void* register_id, void* value) {
const size_t command_size = pw::GetIntSize(command_type);
const size_t register_size = pw::GetIntSize(register_type);
std::byte write_buffer[command_size + register_size];
pw::ConvertToEndian(register_id,
ByteSpan(write_buffer, command_size),
command_type,
kEndianness);
pw::ConvertToEndian(value,
ByteSpan(write_buffer + command_size, register_size),
register_type,
kEndianness);
return i2c_bus_.Write(
i2c_addr_, std::span(write_buffer, command_size + register_size));
}
I2cBus& GetI2cBus() { return i2c_bus_; }
pw::IntSize command_type;
pw::IntSize register_type;
pw::ByteOrder kEndianness;
private:
I2cBus& i2c_bus_;
I2cAddress i2c_addr_;
};
// Represents a register modification within a class that supports
// register reads and writes. Typical sequence of operations on such devices
// is:
// 1. Load existing values
// 2. Modify some bits as defined by the data sheet
// 3. Write the updated value
// Usage:
// RegisterModification m(device, 0x1234);
// m.Bit<0>(true).SetBits<1,4>(3).Commit();
// which is the same as:
// m.SetBit(0, true).SetBitmask(0x1e, 3 << 1).Commit();
// Endianess:
// Device read/write register handles endianess conversions. Assume
// everything is in native endianess (bit 0 is 0x01, bit 4 is 0x10, bit
// 6 is 0x40, bit 11 is 0x800 and so on).
//
// Or if the device returns a modification:
// Status s = device.ModifyRegister(0x1234).SetBits<4,8>(3).Commit();
//
// Commit returns ok only if both the Read (within Modify) and Write
// succeeds. If the read fails, no write is attempted and Commit() will return
// the read failure code.
//
class RegisterModification {
public:
// Initialize a RegisterModification, load the current value of register
RegisterModification(I2cRegisterDevice& device, void* register_id)
: device_(device), register_id_(register_id) {
load_status_ = device_.ReadRegister(register_id_, value_);
}
// Apply all bit operations.
pw::Status Commit() {
if (!load_status_.ok()) {
return load_status_;
}
return device_.WriteRegister(register_id_, value_);
}
// Modify a subset of bits within the stored value.
// The parameters lowBit/highBit are 0-inded bit indices.
// Examples:
// SetBits<0, 3>(foo) translates into "value = (value & ~0x0F) | foo"
// SetBits<2, 3>(foo) translates into "value = (value & ~0x0C) | (foo << 2)"
template <int lowBit, int highBit>
RegisterModification& SetBits(int value) {
PW_CHECK_INT_LE(lowBit, highBit);
PW_CHECK_INT_GE(lowBit, 0);
PW_CHECK_INT_LT(highBit, 8 * GetIntSize(register_type));
constexpr int bit_count = highBit - lowBit + 1;
if (command_type == pw::IntSize::kUint8) {
uint8_t& value_casted = *reinterpret_cast<uint8_t*>(value_);
uint8_t mask = (1 << bit_count) - 1;
PW_CHECK_INT_EQ((value_casted & (~mask)), 0); // check no overflow
value_casted &= ~(mask << lowBit);
value_casted |= (value & mask) << lowBit;
} else if (command_type == pw::IntSize::kUint16) {
uint16_t& value_casted = *reinterpret_cast<uint16_t*>(value_);
uint16_t mask = (1 << bit_count) - 1;
PW_CHECK_INT_EQ((value_casted & (~mask)), 0); // check no overflow
value_casted &= ~(mask << lowBit);
value_casted |= (value & mask) << lowBit;
} else if (command_type == pw::IntSize::kUint32) {
uint32_t& value_casted = *reinterpret_cast<uint32_t*>(value_);
uint32_t mask = (1 << bit_count) - 1;
PW_CHECK_INT_EQ((value_casted & (~mask)), 0); // check no overflow
value_casted &= ~(mask << lowBit);
value_casted |= (value & mask) << lowBit;
} else if (command_type == pw::IntSize::kUint64) {
uint64_t& value_casted = *reinterpret_cast<uint64_t*>(value_);
uint64_t mask = (1 << bit_count) - 1;
PW_CHECK_INT_EQ((value_casted & (~mask)), 0); // check no overflow
value_casted &= ~(mask << lowBit);
value_casted |= (value & mask) << lowBit;
}
return *this;
}
// Set register value according to mask. Value must be already shifted to
// align with mask
RegisterModification& SetBitmask(void* mask, void* set_value) {
if (command_type == pw::IntSize::kUint8) {
uint8_t& value_casted = *reinterpret_cast<uint8_t*>(value_);
uint8_t& mask_casted = *reinterpret_cast<uint8_t*>(mask);
uint8_t& set_value_casted = *reinterpret_cast<uint8_t*>(set_value);
value_casted &= ~mask_casted;
value_casted |= (mask_casted & set_value_casted);
} else if (command_type == pw::IntSize::kUint16) {
uint16_t& value_casted = *reinterpret_cast<uint16_t*>(value_);
uint16_t& mask_casted = *reinterpret_cast<uint16_t*>(mask);
uint16_t& set_value_casted = *reinterpret_cast<uint16_t*>(set_value);
value_casted &= ~mask_casted;
value_casted |= (mask_casted & set_value_casted);
} else if (command_type == pw::IntSize::kUint32) {
uint32_t& value_casted = *reinterpret_cast<uint32_t*>(value_);
uint32_t& mask_casted = *reinterpret_cast<uint32_t*>(mask);
uint32_t& set_value_casted = *reinterpret_cast<uint32_t*>(set_value);
value_casted &= ~mask_casted;
value_casted |= (mask_casted & set_value_casted);
} else if (command_type == pw::IntSize::kUint64) {
uint64_t& value_casted = *reinterpret_cast<uint64_t*>(value_);
uint64_t& mask_casted = *reinterpret_cast<uint64_t*>(mask);
uint64_t& set_value_casted = *reinterpret_cast<uint64_t*>(set_value);
value_casted &= ~mask_casted;
value_casted |= (mask_casted & set_value_casted);
}
return *this;
}
// Set or clear bit based on given value
RegisterModification& SetBit(uint8_t bitNumber, bool value) {
PW_CHECK_INT_LT(bitNumber, 8 * GetIntSize(register_type));
if (command_type == pw::IntSize::kUint8) {
uint8_t mask = 1 << bitNumber;
uint8_t value_shifted = value ? 1 << bitNumber : 0;
return SetBitmask(&mask, &value_shifted);
} else if (command_type == pw::IntSize::kUint16) {
uint16_t mask = 1 << bitNumber;
uint16_t value_shifted = value ? 1 << bitNumber : 0;
return SetBitmask(&mask, &value_shifted);
} else if (command_type == pw::IntSize::kUint32) {
uint32_t mask = 1 << bitNumber;
uint32_t value_shifted = value ? 1 << bitNumber : 0;
return SetBitmask(&mask, &value_shifted);
} else if (command_type == pw::IntSize::kUint64) {
uint64_t mask = 1 << bitNumber;
uint64_t value_shifted = value ? 1 << bitNumber : 0;
return SetBitmask(&mask, &value_shifted);
}
}
// Set or clear bit based on given value
template <int bitNumber>
RegisterModification& Bit(bool value) {
return SetBit(bitNumber, value);
}
pw::IntSize command_type;
pw::IntSize register_type;
private:
I2cRegisterDevice& device_;
void* register_id_;
void* value_;
pw::Status load_status_;
};
} // namespace pw::i2c