i2c: aspeed: Add multi-master use case support
In multi-master environment, this driver's master cannot know exactly when a peer master sends data to this driver's slave so cases can be happened that this master tries sending data through the master_xfer function but slave data from a peer master is still being processed or slave xfer is started by a peer immediately after it queues a master command. To support multi-master use cases properly, this H/W provides arbitration in physical level and it provides priority based command handling too to avoid conflicts in multi-master environment, means that if a master and a slave events happen at the same time, H/W will handle a higher priority event first and a pending event will be handled when bus comes back to the idle state. To support this H/W feature properly, this patch adds the 'pending' state of master and its handling code so that the pending master xfer can be continued after slave operation properly. Signed-off-by: Jae Hyun Yoo <jae.hyun.yoo@linux.intel.com> Reviewed-by: Brendan Higgins <brendanhiggins@google.com> Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
This commit is contained in:
parent
bceb26bffe
commit
2e57b7cebb
|
@ -117,6 +117,7 @@
|
||||||
|
|
||||||
enum aspeed_i2c_master_state {
|
enum aspeed_i2c_master_state {
|
||||||
ASPEED_I2C_MASTER_INACTIVE,
|
ASPEED_I2C_MASTER_INACTIVE,
|
||||||
|
ASPEED_I2C_MASTER_PENDING,
|
||||||
ASPEED_I2C_MASTER_START,
|
ASPEED_I2C_MASTER_START,
|
||||||
ASPEED_I2C_MASTER_TX_FIRST,
|
ASPEED_I2C_MASTER_TX_FIRST,
|
||||||
ASPEED_I2C_MASTER_TX,
|
ASPEED_I2C_MASTER_TX,
|
||||||
|
@ -126,12 +127,13 @@ enum aspeed_i2c_master_state {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum aspeed_i2c_slave_state {
|
enum aspeed_i2c_slave_state {
|
||||||
ASPEED_I2C_SLAVE_STOP,
|
ASPEED_I2C_SLAVE_INACTIVE,
|
||||||
ASPEED_I2C_SLAVE_START,
|
ASPEED_I2C_SLAVE_START,
|
||||||
ASPEED_I2C_SLAVE_READ_REQUESTED,
|
ASPEED_I2C_SLAVE_READ_REQUESTED,
|
||||||
ASPEED_I2C_SLAVE_READ_PROCESSED,
|
ASPEED_I2C_SLAVE_READ_PROCESSED,
|
||||||
ASPEED_I2C_SLAVE_WRITE_REQUESTED,
|
ASPEED_I2C_SLAVE_WRITE_REQUESTED,
|
||||||
ASPEED_I2C_SLAVE_WRITE_RECEIVED,
|
ASPEED_I2C_SLAVE_WRITE_RECEIVED,
|
||||||
|
ASPEED_I2C_SLAVE_STOP,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct aspeed_i2c_bus {
|
struct aspeed_i2c_bus {
|
||||||
|
@ -156,6 +158,8 @@ struct aspeed_i2c_bus {
|
||||||
int cmd_err;
|
int cmd_err;
|
||||||
/* Protected only by i2c_lock_bus */
|
/* Protected only by i2c_lock_bus */
|
||||||
int master_xfer_result;
|
int master_xfer_result;
|
||||||
|
/* Multi-master */
|
||||||
|
bool multi_master;
|
||||||
#if IS_ENABLED(CONFIG_I2C_SLAVE)
|
#if IS_ENABLED(CONFIG_I2C_SLAVE)
|
||||||
struct i2c_client *slave;
|
struct i2c_client *slave;
|
||||||
enum aspeed_i2c_slave_state slave_state;
|
enum aspeed_i2c_slave_state slave_state;
|
||||||
|
@ -251,7 +255,7 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Slave is not currently active, irq was for someone else. */
|
/* Slave is not currently active, irq was for someone else. */
|
||||||
if (bus->slave_state == ASPEED_I2C_SLAVE_STOP)
|
if (bus->slave_state == ASPEED_I2C_SLAVE_INACTIVE)
|
||||||
return irq_handled;
|
return irq_handled;
|
||||||
|
|
||||||
dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
|
dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
|
||||||
|
@ -277,16 +281,15 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
|
||||||
irq_handled |= ASPEED_I2CD_INTR_NORMAL_STOP;
|
irq_handled |= ASPEED_I2CD_INTR_NORMAL_STOP;
|
||||||
bus->slave_state = ASPEED_I2C_SLAVE_STOP;
|
bus->slave_state = ASPEED_I2C_SLAVE_STOP;
|
||||||
}
|
}
|
||||||
if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
|
if (irq_status & ASPEED_I2CD_INTR_TX_NAK &&
|
||||||
|
bus->slave_state == ASPEED_I2C_SLAVE_READ_PROCESSED) {
|
||||||
irq_handled |= ASPEED_I2CD_INTR_TX_NAK;
|
irq_handled |= ASPEED_I2CD_INTR_TX_NAK;
|
||||||
bus->slave_state = ASPEED_I2C_SLAVE_STOP;
|
bus->slave_state = ASPEED_I2C_SLAVE_STOP;
|
||||||
}
|
}
|
||||||
if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
|
|
||||||
irq_handled |= ASPEED_I2CD_INTR_TX_ACK;
|
|
||||||
|
|
||||||
switch (bus->slave_state) {
|
switch (bus->slave_state) {
|
||||||
case ASPEED_I2C_SLAVE_READ_REQUESTED:
|
case ASPEED_I2C_SLAVE_READ_REQUESTED:
|
||||||
if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
|
if (unlikely(irq_status & ASPEED_I2CD_INTR_TX_ACK))
|
||||||
dev_err(bus->dev, "Unexpected ACK on read request.\n");
|
dev_err(bus->dev, "Unexpected ACK on read request.\n");
|
||||||
bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;
|
bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;
|
||||||
i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
|
i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
|
||||||
|
@ -294,9 +297,12 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
|
||||||
writel(ASPEED_I2CD_S_TX_CMD, bus->base + ASPEED_I2C_CMD_REG);
|
writel(ASPEED_I2CD_S_TX_CMD, bus->base + ASPEED_I2C_CMD_REG);
|
||||||
break;
|
break;
|
||||||
case ASPEED_I2C_SLAVE_READ_PROCESSED:
|
case ASPEED_I2C_SLAVE_READ_PROCESSED:
|
||||||
if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK))
|
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_ACK))) {
|
||||||
dev_err(bus->dev,
|
dev_err(bus->dev,
|
||||||
"Expected ACK after processed read.\n");
|
"Expected ACK after processed read.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
irq_handled |= ASPEED_I2CD_INTR_TX_ACK;
|
||||||
i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
|
i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
|
||||||
writel(value, bus->base + ASPEED_I2C_BYTE_BUF_REG);
|
writel(value, bus->base + ASPEED_I2C_BYTE_BUF_REG);
|
||||||
writel(ASPEED_I2CD_S_TX_CMD, bus->base + ASPEED_I2C_CMD_REG);
|
writel(ASPEED_I2CD_S_TX_CMD, bus->base + ASPEED_I2C_CMD_REG);
|
||||||
|
@ -310,10 +316,15 @@ static u32 aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
|
||||||
break;
|
break;
|
||||||
case ASPEED_I2C_SLAVE_STOP:
|
case ASPEED_I2C_SLAVE_STOP:
|
||||||
i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
|
i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
|
||||||
|
bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE;
|
||||||
|
break;
|
||||||
|
case ASPEED_I2C_SLAVE_START:
|
||||||
|
/* Slave was just started. Waiting for the next event. */;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
dev_err(bus->dev, "unhandled slave_state: %d\n",
|
dev_err(bus->dev, "unknown slave_state: %d\n",
|
||||||
bus->slave_state);
|
bus->slave_state);
|
||||||
|
bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -329,6 +340,17 @@ static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus)
|
||||||
u8 slave_addr = i2c_8bit_addr_from_msg(msg);
|
u8 slave_addr = i2c_8bit_addr_from_msg(msg);
|
||||||
|
|
||||||
bus->master_state = ASPEED_I2C_MASTER_START;
|
bus->master_state = ASPEED_I2C_MASTER_START;
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_I2C_SLAVE)
|
||||||
|
/*
|
||||||
|
* If it's requested in the middle of a slave session, set the master
|
||||||
|
* state to 'pending' then H/W will continue handling this master
|
||||||
|
* command when the bus comes back to the idle state.
|
||||||
|
*/
|
||||||
|
if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE)
|
||||||
|
bus->master_state = ASPEED_I2C_MASTER_PENDING;
|
||||||
|
#endif /* CONFIG_I2C_SLAVE */
|
||||||
|
|
||||||
bus->buf_index = 0;
|
bus->buf_index = 0;
|
||||||
|
|
||||||
if (msg->flags & I2C_M_RD) {
|
if (msg->flags & I2C_M_RD) {
|
||||||
|
@ -384,10 +406,6 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
|
||||||
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
|
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
|
||||||
irq_handled |= ASPEED_I2CD_INTR_BUS_RECOVER_DONE;
|
irq_handled |= ASPEED_I2CD_INTR_BUS_RECOVER_DONE;
|
||||||
goto out_complete;
|
goto out_complete;
|
||||||
} else {
|
|
||||||
/* Master is not currently active, irq was for someone else. */
|
|
||||||
if (bus->master_state == ASPEED_I2C_MASTER_INACTIVE)
|
|
||||||
goto out_no_complete;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -399,11 +417,32 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
|
||||||
if (ret) {
|
if (ret) {
|
||||||
dev_dbg(bus->dev, "received error interrupt: 0x%08x\n",
|
dev_dbg(bus->dev, "received error interrupt: 0x%08x\n",
|
||||||
irq_status);
|
irq_status);
|
||||||
|
irq_handled |= (irq_status & ASPEED_I2CD_INTR_MASTER_ERRORS);
|
||||||
|
if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE) {
|
||||||
bus->cmd_err = ret;
|
bus->cmd_err = ret;
|
||||||
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
|
bus->master_state = ASPEED_I2C_MASTER_INACTIVE;
|
||||||
irq_handled |= (irq_status & ASPEED_I2CD_INTR_MASTER_ERRORS);
|
|
||||||
goto out_complete;
|
goto out_complete;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if IS_ENABLED(CONFIG_I2C_SLAVE)
|
||||||
|
/*
|
||||||
|
* A pending master command will be started by H/W when the bus comes
|
||||||
|
* back to idle state after completing a slave operation so change the
|
||||||
|
* master state from 'pending' to 'start' at here if slave is inactive.
|
||||||
|
*/
|
||||||
|
if (bus->master_state == ASPEED_I2C_MASTER_PENDING) {
|
||||||
|
if (bus->slave_state != ASPEED_I2C_SLAVE_INACTIVE)
|
||||||
|
goto out_no_complete;
|
||||||
|
|
||||||
|
bus->master_state = ASPEED_I2C_MASTER_START;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_I2C_SLAVE */
|
||||||
|
|
||||||
|
/* Master is not currently active, irq was for someone else. */
|
||||||
|
if (bus->master_state == ASPEED_I2C_MASTER_INACTIVE ||
|
||||||
|
bus->master_state == ASPEED_I2C_MASTER_PENDING)
|
||||||
|
goto out_no_complete;
|
||||||
|
|
||||||
/* We are in an invalid state; reset bus to a known state. */
|
/* We are in an invalid state; reset bus to a known state. */
|
||||||
if (!bus->msgs) {
|
if (!bus->msgs) {
|
||||||
|
@ -423,6 +462,20 @@ static u32 aspeed_i2c_master_irq(struct aspeed_i2c_bus *bus, u32 irq_status)
|
||||||
* then update the state and handle the new state below.
|
* then update the state and handle the new state below.
|
||||||
*/
|
*/
|
||||||
if (bus->master_state == ASPEED_I2C_MASTER_START) {
|
if (bus->master_state == ASPEED_I2C_MASTER_START) {
|
||||||
|
#if IS_ENABLED(CONFIG_I2C_SLAVE)
|
||||||
|
/*
|
||||||
|
* If a peer master starts a xfer immediately after it queues a
|
||||||
|
* master command, change its state to 'pending' then H/W will
|
||||||
|
* continue the queued master xfer just after completing the
|
||||||
|
* slave mode session.
|
||||||
|
*/
|
||||||
|
if (unlikely(irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH)) {
|
||||||
|
bus->master_state = ASPEED_I2C_MASTER_PENDING;
|
||||||
|
dev_dbg(bus->dev,
|
||||||
|
"master goes pending due to a slave start\n");
|
||||||
|
goto out_no_complete;
|
||||||
|
}
|
||||||
|
#endif /* CONFIG_I2C_SLAVE */
|
||||||
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_ACK))) {
|
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_ACK))) {
|
||||||
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_NAK))) {
|
if (unlikely(!(irq_status & ASPEED_I2CD_INTR_TX_NAK))) {
|
||||||
bus->cmd_err = -ENXIO;
|
bus->cmd_err = -ENXIO;
|
||||||
|
@ -566,7 +619,8 @@ static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
|
||||||
* interrupt bits. Each case needs to be handled using corresponding
|
* interrupt bits. Each case needs to be handled using corresponding
|
||||||
* handlers depending on the current state.
|
* handlers depending on the current state.
|
||||||
*/
|
*/
|
||||||
if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE) {
|
if (bus->master_state != ASPEED_I2C_MASTER_INACTIVE &&
|
||||||
|
bus->master_state != ASPEED_I2C_MASTER_PENDING) {
|
||||||
irq_handled = aspeed_i2c_master_irq(bus, irq_remaining);
|
irq_handled = aspeed_i2c_master_irq(bus, irq_remaining);
|
||||||
irq_remaining &= ~irq_handled;
|
irq_remaining &= ~irq_handled;
|
||||||
if (irq_remaining)
|
if (irq_remaining)
|
||||||
|
@ -601,15 +655,16 @@ static int aspeed_i2c_master_xfer(struct i2c_adapter *adap,
|
||||||
{
|
{
|
||||||
struct aspeed_i2c_bus *bus = i2c_get_adapdata(adap);
|
struct aspeed_i2c_bus *bus = i2c_get_adapdata(adap);
|
||||||
unsigned long time_left, flags;
|
unsigned long time_left, flags;
|
||||||
int ret = 0;
|
|
||||||
|
|
||||||
spin_lock_irqsave(&bus->lock, flags);
|
spin_lock_irqsave(&bus->lock, flags);
|
||||||
bus->cmd_err = 0;
|
bus->cmd_err = 0;
|
||||||
|
|
||||||
/* If bus is busy, attempt recovery. We assume a single master
|
/* If bus is busy in a single master environment, attempt recovery. */
|
||||||
* environment.
|
if (!bus->multi_master &&
|
||||||
*/
|
(readl(bus->base + ASPEED_I2C_CMD_REG) &
|
||||||
if (readl(bus->base + ASPEED_I2C_CMD_REG) & ASPEED_I2CD_BUS_BUSY_STS) {
|
ASPEED_I2CD_BUS_BUSY_STS)) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
spin_unlock_irqrestore(&bus->lock, flags);
|
spin_unlock_irqrestore(&bus->lock, flags);
|
||||||
ret = aspeed_i2c_recover_bus(bus);
|
ret = aspeed_i2c_recover_bus(bus);
|
||||||
if (ret)
|
if (ret)
|
||||||
|
@ -629,9 +684,19 @@ static int aspeed_i2c_master_xfer(struct i2c_adapter *adap,
|
||||||
time_left = wait_for_completion_timeout(&bus->cmd_complete,
|
time_left = wait_for_completion_timeout(&bus->cmd_complete,
|
||||||
bus->adap.timeout);
|
bus->adap.timeout);
|
||||||
|
|
||||||
if (time_left == 0)
|
if (time_left == 0) {
|
||||||
|
/*
|
||||||
|
* If timed out and bus is still busy in a multi master
|
||||||
|
* environment, attempt recovery at here.
|
||||||
|
*/
|
||||||
|
if (bus->multi_master &&
|
||||||
|
(readl(bus->base + ASPEED_I2C_CMD_REG) &
|
||||||
|
ASPEED_I2CD_BUS_BUSY_STS))
|
||||||
|
aspeed_i2c_recover_bus(bus);
|
||||||
|
|
||||||
return -ETIMEDOUT;
|
return -ETIMEDOUT;
|
||||||
else
|
}
|
||||||
|
|
||||||
return bus->master_xfer_result;
|
return bus->master_xfer_result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -672,7 +737,7 @@ static int aspeed_i2c_reg_slave(struct i2c_client *client)
|
||||||
__aspeed_i2c_reg_slave(bus, client->addr);
|
__aspeed_i2c_reg_slave(bus, client->addr);
|
||||||
|
|
||||||
bus->slave = client;
|
bus->slave = client;
|
||||||
bus->slave_state = ASPEED_I2C_SLAVE_STOP;
|
bus->slave_state = ASPEED_I2C_SLAVE_INACTIVE;
|
||||||
spin_unlock_irqrestore(&bus->lock, flags);
|
spin_unlock_irqrestore(&bus->lock, flags);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -827,7 +892,9 @@ static int aspeed_i2c_init(struct aspeed_i2c_bus *bus,
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
if (!of_property_read_bool(pdev->dev.of_node, "multi-master"))
|
if (of_property_read_bool(pdev->dev.of_node, "multi-master"))
|
||||||
|
bus->multi_master = true;
|
||||||
|
else
|
||||||
fun_ctrl_reg |= ASPEED_I2CD_MULTI_MASTER_DIS;
|
fun_ctrl_reg |= ASPEED_I2CD_MULTI_MASTER_DIS;
|
||||||
|
|
||||||
/* Enable Master Mode */
|
/* Enable Master Mode */
|
||||||
|
|
Loading…
Reference in New Issue