blob: 29aa30d2ba89f809150ba161d5a97567c03d59d6 [file] [log] [blame]
/*
* Copyright (c) 2022 Intel Corporation
* Copyright (c) 2023 SILA Embedded Solutions GmbH
* SPDX-License-Identifier: Apache-2.0
*/
#include "smbus_utils.h"
#include <zephyr/logging/log.h>
#include <zephyr/sys/__assert.h>
#include <zephyr/sys/crc.h>
#include <zephyr/sys/util.h>
LOG_MODULE_REGISTER(smbus_utils, CONFIG_SMBUS_LOG_LEVEL);
void smbus_loop_alert_devices(const struct device *dev, sys_slist_t *callbacks)
{
int result;
uint8_t address;
/**
* There might be several peripheral devices which could have triggered the alert and
* the one with the highest priority (lowest address) device wins the arbitration. In
* any case, we will have to loop through all of them.
*
* The format of the transaction is:
*
* 0 1 2
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |S| Alert Addr |R|A| Address |X|N|P|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
while (true) {
result = smbus_byte_read(dev, SMBUS_ADDRESS_ARA, &address);
if (result != 0) {
LOG_DBG("%s: no more peripheral devices left which triggered an alert",
dev->name);
return;
}
LOG_DBG("%s: address 0x%02X triggered an alert", dev->name, address);
smbus_fire_callbacks(callbacks, dev, address);
}
}
#if defined(CONFIG_SMBUS_SOFT_PEC)
uint8_t smbus_pec_num_msgs(uint32_t flags, uint8_t num_msgs)
{
__ASSERT_NO_MSG(num_msgs != 0);
if ((flags & SMBUS_MODE_PEC) == 0) {
return num_msgs - 1;
}
return num_msgs;
}
uint8_t smbus_pec(uint16_t addr, const struct i2c_msg *msgs, uint8_t num_msgs)
{
uint8_t pec = 0;
uint8_t prior_direction = 0;
uint8_t addr8 = addr & BIT_MASK(7);
for (uint8_t i = 0; i < num_msgs; i++) {
/* When direction changes, there is a repeated start byte. */
uint8_t start_byte;
uint8_t direction = msgs[i].flags & I2C_MSG_RW_MASK;
if ((i == 0) || (direction != prior_direction)) {
prior_direction = direction;
start_byte = (addr8 << 1) | direction;
pec = crc8_ccitt(pec, &start_byte, sizeof(start_byte));
}
pec = crc8_ccitt(pec, msgs[i].buf, msgs[i].len);
}
return pec;
}
void smbus_write_prepare_pec(uint32_t flags, uint16_t addr, struct i2c_msg *msgs, uint8_t num_msgs)
{
if ((flags & SMBUS_MODE_PEC) == 0) {
return;
}
__ASSERT_NO_MSG(msgs != NULL);
__ASSERT_NO_MSG(num_msgs != 0);
__ASSERT_NO_MSG(msgs[num_msgs - 1].buf != NULL);
msgs[num_msgs - 1].buf[0] = smbus_pec(addr, msgs, num_msgs - 1);
}
int smbus_read_check_pec(uint32_t flags, uint16_t addr, const struct i2c_msg *msgs,
uint8_t num_msgs)
{
if ((flags & SMBUS_MODE_PEC) == 0) {
return 0;
}
__ASSERT_NO_MSG(num_msgs != 0);
__ASSERT_NO_MSG(msgs != NULL);
__ASSERT_NO_MSG(msgs[num_msgs - 1].buf != NULL);
uint8_t reported_pec = msgs[num_msgs - 1].buf[0];
uint8_t computed_pec = smbus_pec(addr, msgs, num_msgs - 1);
if (reported_pec != computed_pec) {
return -EIO;
}
return 0;
}
#endif /* defined(CONFIG_SMBUS_SOFT_PEC) */