stm32,i2c: Fix large I2C transactions

The stm32's I2C peripheral has a maximum transmission size. Larger trans-
action, that I2C itself allows, can be achieved be using the peripheral's
reload-mode.

In order to do that, st's low-lever drivers need to be informed according-
ly. The previous iteration of the code mishandled the next_msg_flags para-
meter, causing the issue to manifest itself.

This refactors the inner loop of i2c_stm32_transfer() into its own func-
tion. This passes the message parameter by value in order to be able to
mutate its state while keeping the original datum from the user intact
during the entire procedure.

Fixes #43235

Signed-off-by: Frank Terbeck <ft@bewatermyfriend.org>
This commit is contained in:
Frank Terbeck 2022-03-03 20:08:01 +01:00 committed by Carles Cufí
commit 1f0b1403f5

View file

@ -61,6 +61,56 @@ int i2c_stm32_runtime_configure(const struct device *dev, uint32_t config)
return ret; return ret;
} }
static inline int
i2c_stm32_transaction(const struct device *dev,
struct i2c_msg msg, uint8_t *next_msg_flags,
uint16_t periph)
{
/*
* Perform a I2C transaction, while taking into account the STM32 I2C
* peripheral has a limited maximum chunk size. Take appropriate action
* if the message to send exceeds that limit.
*
* The last chunk of a transmission uses this function's next_msg_flags
* parameter for its backend calls (_write/_read). Any previous chunks
* use a copy of the current message's flags, with the STOP and RESTART
* bits turned off. This will cause the backend to use reload-mode,
* which will make the combination of all chunks to look like one big
* transaction on the wire.
*/
const uint32_t i2c_stm32_maxchunk = 255U;
const uint8_t saved_flags = msg.flags;
uint8_t combine_flags =
saved_flags & ~(I2C_MSG_STOP | I2C_MSG_RESTART);
uint8_t *flagsp = NULL;
uint32_t rest = msg.len;
int ret = 0;
do { /* do ... while to allow zero-length transactions */
if (msg.len > i2c_stm32_maxchunk) {
msg.len = i2c_stm32_maxchunk;
msg.flags &= ~I2C_MSG_STOP;
flagsp = &combine_flags;
} else {
msg.flags = saved_flags;
flagsp = next_msg_flags;
}
if ((msg.flags & I2C_MSG_RW_MASK) == I2C_MSG_WRITE) {
ret = stm32_i2c_msg_write(dev, &msg, flagsp, periph);
} else {
ret = stm32_i2c_msg_read(dev, &msg, flagsp, periph);
}
if (ret < 0) {
break;
}
rest -= msg.len;
msg.buf += msg.len;
msg.len = rest;
} while (rest > 0U);
return ret;
}
#define OPERATION(msg) (((struct i2c_msg *) msg)->flags & I2C_MSG_RW_MASK) #define OPERATION(msg) (((struct i2c_msg *) msg)->flags & I2C_MSG_RW_MASK)
static int i2c_stm32_transfer(const struct device *dev, struct i2c_msg *msg, static int i2c_stm32_transfer(const struct device *dev, struct i2c_msg *msg,
@ -126,44 +176,13 @@ static int i2c_stm32_transfer(const struct device *dev, struct i2c_msg *msg,
next = current + 1; next = current + 1;
next_msg_flags = &(next->flags); next_msg_flags = &(next->flags);
} }
do { ret = i2c_stm32_transaction(dev, *current, next_msg_flags, slave);
uint32_t temp_len = current->len; if (ret < 0)
uint8_t tmp_msg_flags = current->flags & ~I2C_MSG_RESTART; break;
uint8_t tmp_next_msg_flags = next_msg_flags ?
*next_msg_flags : 0;
if (current->len > 255) {
current->len = 255U;
current->flags &= ~I2C_MSG_STOP;
if (next_msg_flags) {
*next_msg_flags = current->flags &
~I2C_MSG_RESTART;
}
}
if ((current->flags & I2C_MSG_RW_MASK) ==
I2C_MSG_WRITE) {
ret = stm32_i2c_msg_write(dev, current,
next_msg_flags,
slave);
} else {
ret = stm32_i2c_msg_read(dev, current,
next_msg_flags, slave);
}
if (ret < 0) {
goto exit;
}
if (next_msg_flags) {
*next_msg_flags = tmp_next_msg_flags;
}
current->buf += current->len;
current->flags = tmp_msg_flags;
current->len = temp_len - current->len;
} while (current->len > 0);
current++; current++;
num_msgs--; num_msgs--;
} }
exit:
k_sem_give(&data->bus_mutex); k_sem_give(&data->bus_mutex);
return ret; return ret;
} }