From 0123e5ee51e2edd1fd1a4ae1c036f813061c369f Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sun, 28 Sep 2025 18:37:28 -0400 Subject: [PATCH 1/4] drivers: smbus: provide packet-error-checking (pec) routines In the case that SMBus hardware does not automatically perform packet error checking (PEC), provide generic inline functions in `zephyr/drivers/smbus.h` that can be used by drivers to perform PEC in software. Signed-off-by: Chris Friedt Signed-off-by: Andrew Lewycky --- drivers/smbus/Kconfig | 1 + drivers/smbus/smbus_utils.c | 71 ++++++++++++++++++++++++++++++++ drivers/smbus/smbus_utils.h | 81 +++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+) diff --git a/drivers/smbus/Kconfig b/drivers/smbus/Kconfig index 66ca5df627dfe..d29d02a10ab58 100644 --- a/drivers/smbus/Kconfig +++ b/drivers/smbus/Kconfig @@ -5,6 +5,7 @@ menuconfig SMBUS bool "System Management Bus (SMBus) drivers" + imply CRC help Enable SMBus Driver Configuration diff --git a/drivers/smbus/smbus_utils.c b/drivers/smbus/smbus_utils.c index 5ff3076da9e16..cd44db938db27 100644 --- a/drivers/smbus/smbus_utils.c +++ b/drivers/smbus/smbus_utils.c @@ -7,6 +7,9 @@ #include "smbus_utils.h" #include +#include +#include +#include LOG_MODULE_REGISTER(smbus_utils, CONFIG_SMBUS_LOG_LEVEL); @@ -41,3 +44,71 @@ void smbus_loop_alert_devices(const struct device *dev, sys_slist_t *callbacks) smbus_fire_callbacks(callbacks, dev, address); } } + +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; +} diff --git a/drivers/smbus/smbus_utils.h b/drivers/smbus/smbus_utils.h index a4f7ecd0c7292..56b6713a997e5 100644 --- a/drivers/smbus/smbus_utils.h +++ b/drivers/smbus/smbus_utils.h @@ -7,8 +7,11 @@ #ifndef ZEPHYR_DRIVERS_SMBUS_SMBUS_UTILS_H_ #define ZEPHYR_DRIVERS_SMBUS_SMBUS_UTILS_H_ +#include #include + #include +#include #include #include @@ -107,4 +110,82 @@ static inline void smbus_init_callback(struct smbus_callback *callback, */ void smbus_loop_alert_devices(const struct device *dev, sys_slist_t *callbacks); +/** + * @brief Compute the number of messages required for an SMBus transaction + * + * If @p flags indicates that the transaction requires packet error checking (PEC), + * the number of messages is equal to @p num_msgs, otherwise the number of messages + * required is @p num_msgs - 1, since a PEC byte is not required. + * + * Callers are expected to allocated an array of @ref i2c_msg objects to hold a number of + * i2c messages, including one message dedicated to the a PEC byte, whether or not PEC is + * being used. + * + * @note This function is intended for SMBus drivers that do not have hardware PEC support and + * therefore must rely on software PEC calculation. + * + * @param flags SMBus flags. + * @param num_msgs Number of allocated messages. + * @return The number of required messages. + */ +uint8_t smbus_pec_num_msgs(uint32_t flags, uint8_t num_msgs); + +/** + * @brief Compute the packet error checking (PEC) byte for an SMBus transaction + * + * @note This function is intended for SMBus drivers that do not have hardware PEC support and + * therefore must rely on software PEC calculation. + * + * @note At this time, only 7-bit addresses are supported in @p addr. + * + * @param addr The address of the target device. + * @param msgs Array of @ref i2c_msg that make up the transaction. + * @param num_msgs Number of messages in the transaction. + * @return the computed PEC byte. + */ +uint8_t smbus_pec(uint16_t addr, const struct i2c_msg *msgs, uint8_t num_msgs); + +/** + * @brief Prepare the packet error checking (PEC) byte for an SMBus write transaction + * + * If the @p flags bitmask contains @ref SMBUS_MODE_PEC (i.e. PEC is enabled), the number of + * messages is equal to @p num_msgs, otherwise the number of messages required is @p num_msgs - 1, + * since a PEC byte is not required. + * + * @note This function is intended for SMBus drivers that do not have hardware PEC support and + * therefore must rely on software PEC calculation. + * + * @note At this time, only 7-bit addresses are supported in @p addr. + * + * @param flags SMBus flags. + * @param addr The address of the target device. + * @param messages Array of @ref i2c_msg objects that make up the transaction. + * @param num_msgs Number of messages in the transaction. + */ +void smbus_write_prepare_pec(uint32_t flags, uint16_t addr, struct i2c_msg *msgs, uint8_t num_msgs); + +/** + * @brief Check the packet error checking (PEC) byte for an SMBus read transaction + * + * If the @p flags bitmask contains @ref SMBUS_MODE_PEC (i.e. PEC is enabled), the number of + * messages is equal to @p num_msgs, otherwise the number of messages required is + * @p num_msgs - 1, since a PEC byte is not required. + * + * When PEC is not enabled, this function returns 0. + * + * @note This function is intended for SMBus drivers that do not have hardware PEC support and + * therefore must rely on software PEC calculation. + * + * @note At this time, only 7-bit addresses are supported in @p addr. + * + * @param flags SMBus flags. + * @param addr The address of the target device. + * @param messages Array of @ref i2c_msg objects that make up the transaction. + * @param num_msgs Number of messages in the transaction. + * @retval 0 on success. + * @retval -EIO if the PEC byte does not match the computed value. + */ +int smbus_read_check_pec(uint32_t flags, uint16_t addr, const struct i2c_msg *msgs, + uint8_t num_msgs); + #endif /* ZEPHYR_DRIVERS_SMBUS_SMBUS_UTILS_H_ */ From 1d3de902561d1bd4c17232eeaefa180d651a02e8 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Fri, 3 Oct 2025 11:03:48 -0400 Subject: [PATCH 2/4] tests: drivers: smbus: add packet error correction (pec) testsuite Add tests for SMBus packet error correction (PEC). The two primary utilities tested are * smbus_pec(): compute the PEC byte for a Block Write * smbus_read_check_pec(): verify a PEC after * Read Byte * Read Word * Block Read Signed-off-by: Chris Friedt --- tests/drivers/smbus/smbus_pec/CMakeLists.txt | 10 ++ tests/drivers/smbus/smbus_pec/prj.conf | 3 + tests/drivers/smbus/smbus_pec/src/main.c | 133 +++++++++++++++++++ tests/drivers/smbus/smbus_pec/testcase.yaml | 4 + 4 files changed, 150 insertions(+) create mode 100644 tests/drivers/smbus/smbus_pec/CMakeLists.txt create mode 100644 tests/drivers/smbus/smbus_pec/prj.conf create mode 100644 tests/drivers/smbus/smbus_pec/src/main.c create mode 100644 tests/drivers/smbus/smbus_pec/testcase.yaml diff --git a/tests/drivers/smbus/smbus_pec/CMakeLists.txt b/tests/drivers/smbus/smbus_pec/CMakeLists.txt new file mode 100644 index 0000000000000..c23f2b644532f --- /dev/null +++ b/tests/drivers/smbus/smbus_pec/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (c) 2025 Tenstorrent AI ULC +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(smbus_pec) + +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/drivers/smbus) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/drivers/smbus/smbus_pec/prj.conf b/tests/drivers/smbus/smbus_pec/prj.conf new file mode 100644 index 0000000000000..3072471bfddc9 --- /dev/null +++ b/tests/drivers/smbus/smbus_pec/prj.conf @@ -0,0 +1,3 @@ +CONFIG_ZTEST=y + +CONFIG_SMBUS=y diff --git a/tests/drivers/smbus/smbus_pec/src/main.c b/tests/drivers/smbus/smbus_pec/src/main.c new file mode 100644 index 0000000000000..e55908e5b9481 --- /dev/null +++ b/tests/drivers/smbus/smbus_pec/src/main.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2025 Tenstorrent AI ULC + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include + +ZTEST(smbus_pec, test_smbus_pec) +{ + uint8_t addr = 0x42; + /* Write Block with PEC (SMBus spec v3.1, Section 6.5.7) */ + uint8_t write_data[] = { + 0x73, /* command */ + 4, /* len */ + 0xde, 0xad, 0xbe, 0xef, /* data */ + }; + struct i2c_msg msgs[] = { + { + .buf = write_data, + .len = sizeof(write_data), + .flags = I2C_MSG_WRITE | I2C_MSG_STOP, + }, + /* driver would add a PEC message here */ + }; + + uint8_t actual_pec = smbus_pec(addr, msgs, ARRAY_SIZE(msgs)); + uint8_t expected_pec = 0x12; + + zexpect_equal(expected_pec, actual_pec, "expected: %02x actual: %02x", + expected_pec, actual_pec); +} + +ZTEST(smbus_pec, test_smbus_read_check_pec) +{ + uint8_t addr = 0xa; + + { + /* Read Byte with PEC (SMBus spec v3.1, Section 6.5.5) */ + uint8_t data[] = { + 0x10, /* command */ + 0x05, /* data */ + 0x90, /* PEC */ + }; + struct i2c_msg msgs[] = { + { + .buf = &data[0], /* command */ + .len = 1, + .flags = I2C_MSG_WRITE, + }, + { + .buf = &data[1], /* data */ + .len = 1, + .flags = I2C_MSG_READ, + }, + { + .buf = &data[2], /* PEC */ + .len = 1, + .flags = I2C_MSG_READ, + }, + }; + + zexpect_ok(smbus_read_check_pec(SMBUS_MODE_PEC, addr, msgs, ARRAY_SIZE(msgs))); + } + + { + /* Read Word with PEC (SMBus spec v3.1, Section 6.5.5) */ + uint8_t data[] = { + 0x10, /* command */ + 0x05, /* data byte (low) */ + 0x0a, /* data byte (high) */ + 0xcf, /* PEC */ + }; + struct i2c_msg msgs[] = { + { + .buf = &data[0], /* command */ + .len = 1, + .flags = I2C_MSG_WRITE, + }, + { + .buf = &data[1], /* data */ + .len = 2, + .flags = I2C_MSG_READ, + }, + { + .buf = &data[3], /* PEC */ + .len = 1, + .flags = I2C_MSG_READ, + }, + }; + + zexpect_ok(smbus_read_check_pec(SMBUS_MODE_PEC, addr, msgs, ARRAY_SIZE(msgs))); + } + + { + /* Block read (SMBus spec v3.1, Section 6.5.7) */ + uint8_t data[] = { + 0x10, /* command */ + 0x06, /* block count */ + 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, /* data */ + 0x99, /* PEC */ + }; + struct i2c_msg msgs[] = { + { + .buf = &data[0], /* command */ + .len = 1, + .flags = I2C_MSG_WRITE, + }, + { + .buf = &data[1], /* block count */ + .len = 1, + .flags = I2C_MSG_READ | I2C_MSG_RESTART, + }, + { + .buf = &data[2], /* data */ + .len = data[1], + .flags = I2C_MSG_READ, + }, + { + .buf = &data[2] + data[1], /* PEC */ + .len = 1, + .flags = I2C_MSG_READ, + }, + }; + + zexpect_ok(smbus_read_check_pec(SMBUS_MODE_PEC, addr, msgs, ARRAY_SIZE(msgs))); + } +} + +ZTEST_SUITE(smbus_pec, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/drivers/smbus/smbus_pec/testcase.yaml b/tests/drivers/smbus/smbus_pec/testcase.yaml new file mode 100644 index 0000000000000..81c1fcd06f841 --- /dev/null +++ b/tests/drivers/smbus/smbus_pec/testcase.yaml @@ -0,0 +1,4 @@ +common: + tags: smbus +tests: + drivers.smbus.pec: {} From d34b26ad247d31381fb5f4e045c9b3c272a836b5 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sun, 28 Sep 2025 18:43:09 -0400 Subject: [PATCH 3/4] drivers: smbus: stm32: support packet-error-checking (pec) Add support for SMBus packet error checking (PEC) to the stm32 driver. This feature allows SMBust communication to be slightly more robust in the presence of noise, in that packet errors can be detected on the receive side. Signed-off-by: Andrew Lewycky Signed-off-by: Chris Friedt --- drivers/smbus/smbus_stm32.c | 231 ++++++++++++++++++++++++++++++------ 1 file changed, 195 insertions(+), 36 deletions(-) diff --git a/drivers/smbus/smbus_stm32.c b/drivers/smbus/smbus_stm32.c index c421c1c4504a7..c127ccb504682 100644 --- a/drivers/smbus/smbus_stm32.c +++ b/drivers/smbus/smbus_stm32.c @@ -97,11 +97,6 @@ static int smbus_stm32_configure(const struct device *dev, uint32_t config_value const struct smbus_stm32_config *config = dev->config; struct smbus_stm32_data *data = dev->data; - if (config_value & SMBUS_MODE_PEC) { - LOG_ERR("%s: not implemented", dev->name); - return -EINVAL; - } - if (config_value & SMBUS_MODE_HOST_NOTIFY) { LOG_ERR("%s: not available", dev->name); return -EINVAL; @@ -152,62 +147,200 @@ static int smbus_stm32_quick(const struct device *dev, uint16_t periph_addr, static int smbus_stm32_byte_write(const struct device *dev, uint16_t periph_addr, uint8_t command) { + uint8_t pec; + uint8_t num_msgs; + struct i2c_msg msgs[] = { + { + .buf = &command, + .len = sizeof(command), + .flags = I2C_MSG_WRITE, + }, + { + .buf = &pec, + .len = sizeof(pec), + .flags = I2C_MSG_WRITE, + }, + }; + struct smbus_stm32_data *data = dev->data; const struct smbus_stm32_config *config = dev->config; - return i2c_write(config->i2c_dev, &command, sizeof(command), periph_addr); + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(msgs)); + smbus_write_prepare_pec(data->config, periph_addr, msgs, num_msgs); + return i2c_transfer(config->i2c_dev, msgs, num_msgs, periph_addr); } static int smbus_stm32_byte_read(const struct device *dev, uint16_t periph_addr, uint8_t *byte) { + int ret; + uint8_t pec = 0; + uint8_t num_msgs; + struct i2c_msg msgs[] = { + { + .buf = byte, + .len = sizeof(*byte), + .flags = I2C_MSG_READ, + }, + { + .buf = &pec, + .len = sizeof(pec), + .flags = I2C_MSG_READ, + }, + }; + struct smbus_stm32_data *data = dev->data; const struct smbus_stm32_config *config = dev->config; - return i2c_read(config->i2c_dev, byte, sizeof(*byte), periph_addr); + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(msgs)); + ret = i2c_transfer(config->i2c_dev, msgs, num_msgs, periph_addr); + if (ret < 0) { + return ret; + } + + ret = smbus_read_check_pec(data->config, periph_addr, msgs, num_msgs); + if (ret < 0) { + return ret; + } + + return 0; } static int smbus_stm32_byte_data_write(const struct device *dev, uint16_t periph_addr, uint8_t command, uint8_t byte) { - const struct smbus_stm32_config *config = dev->config; - uint8_t buffer[] = { - command, - byte, + uint8_t pec; + uint8_t num_msgs; + struct i2c_msg msgs[] = { + { + .buf = &command, + .len = sizeof(command), + .flags = I2C_MSG_WRITE, + }, + { + .buf = &byte, + .len = sizeof(byte), + .flags = I2C_MSG_WRITE, + }, + { + .buf = &pec, + .len = sizeof(pec), + .flags = I2C_MSG_WRITE, + }, }; + struct smbus_stm32_data *data = dev->data; + const struct smbus_stm32_config *config = dev->config; - return i2c_write(config->i2c_dev, buffer, ARRAY_SIZE(buffer), periph_addr); + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(msgs)); + smbus_write_prepare_pec(data->config, periph_addr, msgs, num_msgs); + return i2c_transfer(config->i2c_dev, msgs, num_msgs, periph_addr); } static int smbus_stm32_byte_data_read(const struct device *dev, uint16_t periph_addr, uint8_t command, uint8_t *byte) { + int ret; + uint8_t pec; + uint8_t num_msgs; + struct i2c_msg msgs[] = { + { + .buf = &command, + .len = sizeof(command), + .flags = I2C_MSG_WRITE, + }, + { + .buf = byte, + .len = sizeof(*byte), + .flags = I2C_MSG_READ | I2C_MSG_RESTART, + }, + { + .buf = &pec, + .len = sizeof(pec), + .flags = I2C_MSG_READ, + }, + }; + struct smbus_stm32_data *data = dev->data; const struct smbus_stm32_config *config = dev->config; - return i2c_write_read(config->i2c_dev, periph_addr, &command, sizeof(command), byte, - sizeof(*byte)); + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(msgs)); + ret = i2c_transfer(config->i2c_dev, msgs, num_msgs, periph_addr); + if (ret < 0) { + return ret; + } + + ret = smbus_read_check_pec(data->config, periph_addr, msgs, num_msgs); + if (ret < 0) { + return ret; + } + + return 0; } static int smbus_stm32_word_data_write(const struct device *dev, uint16_t periph_addr, uint8_t command, uint16_t word) { + uint8_t pec; + uint8_t num_msgs; + struct i2c_msg msgs[] = { + { + .buf = &command, + .len = sizeof(command), + .flags = I2C_MSG_WRITE, + }, + { + .buf = (uint8_t *)&word, + .len = sizeof(word), + .flags = I2C_MSG_WRITE, + }, + { + .buf = &pec, + .len = sizeof(pec), + .flags = I2C_MSG_WRITE, + }, + }; + struct smbus_stm32_data *data = dev->data; const struct smbus_stm32_config *config = dev->config; - uint8_t buffer[sizeof(command) + sizeof(word)]; - - buffer[0] = command; - sys_put_le16(word, buffer + 1); - return i2c_write(config->i2c_dev, buffer, ARRAY_SIZE(buffer), periph_addr); + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(msgs)); + smbus_write_prepare_pec(data->config, periph_addr, msgs, num_msgs); + return i2c_transfer(config->i2c_dev, msgs, num_msgs, periph_addr); } static int smbus_stm32_word_data_read(const struct device *dev, uint16_t periph_addr, uint8_t command, uint16_t *word) { + int ret; + uint8_t pec; + uint8_t num_msgs; + struct i2c_msg messages[] = { + { + .buf = &command, + .len = sizeof(command), + .flags = I2C_MSG_WRITE, + }, + { + .buf = (uint8_t *)word, + .len = sizeof(*word), + .flags = I2C_MSG_READ | I2C_MSG_RESTART, + }, + { + .buf = &pec, + .len = sizeof(pec), + .flags = I2C_MSG_READ, + }, + }; + struct smbus_stm32_data *data = dev->data; const struct smbus_stm32_config *config = dev->config; - int result; - result = i2c_write_read(config->i2c_dev, periph_addr, &command, sizeof(command), word, - sizeof(*word)); - *word = sys_le16_to_cpu(*word); + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(messages)); + ret = i2c_transfer(config->i2c_dev, messages, num_msgs, periph_addr); + if (ret < 0) { + return ret; + } - return result; + ret = smbus_read_check_pec(data->config, periph_addr, messages, num_msgs); + if (ret < 0) { + return ret; + } + + return 0; } static int smbus_stm32_pcall(const struct device *dev, uint16_t periph_addr, uint8_t command, @@ -230,8 +363,9 @@ static int smbus_stm32_pcall(const struct device *dev, uint16_t periph_addr, uin static int smbus_stm32_block_write(const struct device *dev, uint16_t periph_addr, uint8_t command, uint8_t count, uint8_t *buf) { - const struct smbus_stm32_config *config = dev->config; - struct i2c_msg messages[] = { + uint8_t pec; + uint8_t num_msgs; + struct i2c_msg msgs[] = { { .buf = &command, .len = sizeof(command), @@ -246,18 +380,28 @@ static int smbus_stm32_block_write(const struct device *dev, uint16_t periph_add .buf = buf, .len = count, .flags = I2C_MSG_WRITE, - } + }, + { + .buf = &pec, + .len = 1, + .flags = I2C_MSG_WRITE, + }, }; + struct smbus_stm32_data *data = dev->data; + const struct smbus_stm32_config *config = dev->config; - return i2c_transfer(config->i2c_dev, messages, ARRAY_SIZE(messages), periph_addr); + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(msgs)); + smbus_write_prepare_pec(data->config, periph_addr, msgs, ARRAY_SIZE(msgs)); + return i2c_transfer(config->i2c_dev, msgs, num_msgs, periph_addr); } static int smbus_stm32_block_read(const struct device *dev, uint16_t periph_addr, uint8_t command, uint8_t *count, uint8_t *buf) { - const struct smbus_stm32_config *config = dev->config; - - struct i2c_msg messages[] = { + int ret; + uint8_t num_msgs; + uint8_t received_pec; + struct i2c_msg msgs[] = { { .buf = &command, .len = sizeof(command), @@ -272,20 +416,35 @@ static int smbus_stm32_block_read(const struct device *dev, uint16_t periph_addr .buf = buf, .len = 0, /* written by previous message! */ .flags = I2C_MSG_READ, - } + }, + { + .buf = &received_pec, + .len = 1, + .flags = I2C_MSG_READ, + }, }; + struct smbus_stm32_data *data = dev->data; + const struct smbus_stm32_config *config = dev->config; /* Count is read in msg 1 and stored in the len of msg 2. * This works because the STM I2C driver processes each message serially. * The addressing math assumes little-endian. */ - messages[1].buf = (uint8_t *)&messages[2].len; + msgs[1].buf = (uint8_t *)&msgs[2].len; - int res = i2c_transfer(config->i2c_dev, messages, ARRAY_SIZE(messages), periph_addr); + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(msgs)); + ret = i2c_transfer(config->i2c_dev, msgs, num_msgs, periph_addr); + if (ret < 0) { + return ret; + } - *count = messages[2].len; + *count = msgs[2].len; + ret = smbus_read_check_pec(data->config, periph_addr, msgs, num_msgs); + if (ret < 0) { + return ret; + } - return res; + return 0; } static DEVICE_API(smbus, smbus_stm32_api) = { From 021b6e2413c6d67c49abe99de05e7f33f9c1c6b3 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Wed, 8 Oct 2025 08:26:30 -0400 Subject: [PATCH 4/4] drivers: smbus: stm32: add support for pcall api Add SMBus Process Call API for STM32. The Process Call API is very useful, as it sends a command (a 16-bit word split over two data bytes) to the target, and then waits for the target to return a result (similarly a 16-bit word) dependent on the command. This implementation also supports PEC mode (packet error checking) and is dependent on PEC support already being supported in-tree. Signed-off-by: James Growden Signed-off-by: Chris Friedt --- drivers/smbus/smbus_stm32.c | 66 ++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/drivers/smbus/smbus_stm32.c b/drivers/smbus/smbus_stm32.c index c127ccb504682..ebcdb26e07330 100644 --- a/drivers/smbus/smbus_stm32.c +++ b/drivers/smbus/smbus_stm32.c @@ -447,6 +447,70 @@ static int smbus_stm32_block_read(const struct device *dev, uint16_t periph_addr return 0; } +int smbus_stm32_block_pcall(const struct device *dev, uint16_t addr, uint8_t cmd, + uint8_t send_count, uint8_t *send_buf, uint8_t *recv_count, + uint8_t *recv_buf) +{ + int ret; + uint8_t num_msgs; + uint8_t received_pec; + struct i2c_msg msgs[] = { + { + .buf = &cmd, + .len = sizeof(cmd), + .flags = I2C_MSG_WRITE, + }, + { + .buf = &send_count, + .len = sizeof(send_count), + .flags = I2C_MSG_WRITE, + }, + { + .buf = send_buf, + .len = send_count, + .flags = I2C_MSG_WRITE, + }, + { + .buf = NULL, /* will point to next message's len field */ + .len = 1, + .flags = I2C_MSG_READ | I2C_MSG_RESTART, + }, + { + .buf = recv_buf, + .len = 0, /* written by previous message! */ + .flags = I2C_MSG_READ, + }, + { + .buf = &received_pec, + .len = 1, + .flags = I2C_MSG_READ, + }, + }; + struct smbus_stm32_data *data = dev->data; + const struct smbus_stm32_config *config = dev->config; + + /* Count is read in msg 3 and stored in the len of msg 4. + * This works because the STM I2C driver processes each message serially. + * The addressing math assumes little-endian. + */ + msgs[3].buf = (uint8_t *)&msgs[4].len; + + num_msgs = smbus_pec_num_msgs(data->config, ARRAY_SIZE(msgs)); + ret = i2c_transfer(config->i2c_dev, msgs, num_msgs, addr); + if (ret < 0) { + return ret; + } + + ret = smbus_read_check_pec(data->config, addr, msgs, num_msgs); + if (ret < 0) { + return ret; + } + + *recv_count = msgs[4].len; + + return 0; +} + static DEVICE_API(smbus, smbus_stm32_api) = { .configure = smbus_stm32_configure, .get_config = smbus_stm32_get_config, @@ -467,7 +531,7 @@ static DEVICE_API(smbus, smbus_stm32_api) = { .smbus_smbalert_set_cb = NULL, .smbus_smbalert_remove_cb = NULL, #endif /* CONFIG_SMBUS_STM32_SMBALERT */ - .smbus_block_pcall = NULL, + .smbus_block_pcall = smbus_stm32_block_pcall, .smbus_host_notify_set_cb = NULL, .smbus_host_notify_remove_cb = NULL, };