i2c: microchip-core: actually use repeated sends

BugLink: https://bugs.launchpad.net/bugs/2103869

commit 9a8f9320d67b27ddd7f1ee88d91820197a0e908f upstream.

At present, where repeated sends are intended to be used, the
i2c-microchip-core driver sends a stop followed by a start. Lots of i2c
devices must not malfunction in the face of this behaviour, because the
driver has operated like this for years! Try to keep track of whether or
not a repeated send is required, and suppress sending a stop in these
cases.

CC: stable@vger.kernel.org
Fixes: 64a6f1c498 ("i2c: add support for microchip fpga i2c controllers")
Signed-off-by: Conor Dooley <conor.dooley@microchip.com>
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20241218-football-composure-e56df2461461@spud
Signed-off-by: Andi Shyti <andi.shyti@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Manuel Diewald <manuel.diewald@canonical.com>
Signed-off-by: Mehmet Basaran <mehmet.basaran@canonical.com>
This commit is contained in:
Conor Dooley
2024-12-18 12:07:40 +00:00
committed by Mehmet Basaran
parent 298dab371b
commit ed3044c550
+96 -28
View File
@@ -93,27 +93,35 @@
* @base: pointer to register struct
* @dev: device reference
* @i2c_clk: clock reference for i2c input clock
* @msg_queue: pointer to the messages requiring sending
* @buf: pointer to msg buffer for easier use
* @msg_complete: xfer completion object
* @adapter: core i2c abstraction
* @msg_err: error code for completed message
* @bus_clk_rate: current i2c bus clock rate
* @isr_status: cached copy of local ISR status
* @total_num: total number of messages to be sent/received
* @current_num: index of the current message being sent/received
* @msg_len: number of bytes transferred in msg
* @addr: address of the current slave
* @restart_needed: whether or not a repeated start is required after current message
*/
struct mchp_corei2c_dev {
void __iomem *base;
struct device *dev;
struct clk *i2c_clk;
struct i2c_msg *msg_queue;
u8 *buf;
struct completion msg_complete;
struct i2c_adapter adapter;
int msg_err;
int total_num;
int current_num;
u32 bus_clk_rate;
u32 isr_status;
u16 msg_len;
u8 addr;
bool restart_needed;
};
static void mchp_corei2c_core_disable(struct mchp_corei2c_dev *idev)
@@ -222,6 +230,47 @@ static int mchp_corei2c_fill_tx(struct mchp_corei2c_dev *idev)
return 0;
}
static void mchp_corei2c_next_msg(struct mchp_corei2c_dev *idev)
{
struct i2c_msg *this_msg;
u8 ctrl;
if (idev->current_num >= idev->total_num) {
complete(&idev->msg_complete);
return;
}
/*
* If there's been an error, the isr needs to return control
* to the "main" part of the driver, so as not to keep sending
* messages once it completes and clears the SI bit.
*/
if (idev->msg_err) {
complete(&idev->msg_complete);
return;
}
this_msg = idev->msg_queue++;
if (idev->current_num < (idev->total_num - 1)) {
struct i2c_msg *next_msg = idev->msg_queue;
idev->restart_needed = next_msg->flags & I2C_M_RD;
} else {
idev->restart_needed = false;
}
idev->addr = i2c_8bit_addr_from_msg(this_msg);
idev->msg_len = this_msg->len;
idev->buf = this_msg->buf;
ctrl = readb(idev->base + CORE_I2C_CTRL);
ctrl |= CTRL_STA;
writeb(ctrl, idev->base + CORE_I2C_CTRL);
idev->current_num++;
}
static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev)
{
u32 status = idev->isr_status;
@@ -247,10 +296,14 @@ static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev)
break;
case STATUS_M_SLAW_ACK:
case STATUS_M_TX_DATA_ACK:
if (idev->msg_len > 0)
if (idev->msg_len > 0) {
mchp_corei2c_fill_tx(idev);
else
last_byte = true;
} else {
if (idev->restart_needed)
finished = true;
else
last_byte = true;
}
break;
case STATUS_M_TX_DATA_NACK:
case STATUS_M_SLAR_NACK:
@@ -287,7 +340,7 @@ static irqreturn_t mchp_corei2c_handle_isr(struct mchp_corei2c_dev *idev)
mchp_corei2c_stop(idev);
if (last_byte || finished)
complete(&idev->msg_complete);
mchp_corei2c_next_msg(idev);
return IRQ_HANDLED;
}
@@ -311,21 +364,48 @@ static irqreturn_t mchp_corei2c_isr(int irq, void *_dev)
return ret;
}
static int mchp_corei2c_xfer_msg(struct mchp_corei2c_dev *idev,
struct i2c_msg *msg)
static int mchp_corei2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num)
{
u8 ctrl;
struct mchp_corei2c_dev *idev = i2c_get_adapdata(adap);
struct i2c_msg *this_msg = msgs;
unsigned long time_left;
idev->addr = i2c_8bit_addr_from_msg(msg);
idev->msg_len = msg->len;
idev->buf = msg->buf;
idev->msg_err = 0;
reinit_completion(&idev->msg_complete);
u8 ctrl;
mchp_corei2c_core_enable(idev);
/*
* The isr controls the flow of a transfer, this info needs to be saved
* to a location that it can access the queue information from.
*/
idev->restart_needed = false;
idev->msg_queue = msgs;
idev->total_num = num;
idev->current_num = 0;
/*
* But the first entry to the isr is triggered by the start in this
* function, so the first message needs to be "dequeued".
*/
idev->addr = i2c_8bit_addr_from_msg(this_msg);
idev->msg_len = this_msg->len;
idev->buf = this_msg->buf;
idev->msg_err = 0;
if (idev->total_num > 1) {
struct i2c_msg *next_msg = msgs + 1;
idev->restart_needed = next_msg->flags & I2C_M_RD;
}
idev->current_num++;
idev->msg_queue++;
reinit_completion(&idev->msg_complete);
/*
* Send the first start to pass control to the isr
*/
ctrl = readb(idev->base + CORE_I2C_CTRL);
ctrl |= CTRL_STA;
writeb(ctrl, idev->base + CORE_I2C_CTRL);
@@ -335,20 +415,8 @@ static int mchp_corei2c_xfer_msg(struct mchp_corei2c_dev *idev,
if (!time_left)
return -ETIMEDOUT;
return idev->msg_err;
}
static int mchp_corei2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
int num)
{
struct mchp_corei2c_dev *idev = i2c_get_adapdata(adap);
int i, ret;
for (i = 0; i < num; i++) {
ret = mchp_corei2c_xfer_msg(idev, msgs++);
if (ret)
return ret;
}
if (idev->msg_err)
return idev->msg_err;
return num;
}