Skip to content

Commit

Permalink
i2c: aspeed: added slave support for Aspeed I2C driver
Browse files Browse the repository at this point in the history
Added slave support for Aspeed I2C controller. Supports fourteen busses
present in AST24XX and AST25XX BMC SoCs by Aspeed.

Signed-off-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Wolfram Sang <wsa@the-dreams.de>
  • Loading branch information
Brendan Higgins authored and Wolfram Sang committed Jun 23, 2017
1 parent f327c68 commit f9eb913
Showing 1 changed file with 201 additions and 0 deletions.
201 changes: 201 additions & 0 deletions drivers/i2c/busses/i2c-aspeed.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
#define ASPEED_I2CD_SDA_DRIVE_1T_EN BIT(8)
#define ASPEED_I2CD_M_SDA_DRIVE_1T_EN BIT(7)
#define ASPEED_I2CD_M_HIGH_SPEED_EN BIT(6)
#define ASPEED_I2CD_SLAVE_EN BIT(1)
#define ASPEED_I2CD_MASTER_EN BIT(0)

/* 0x04 : I2CD Clock and AC Timing Control Register #1 */
Expand All @@ -69,6 +70,7 @@
*/
#define ASPEED_I2CD_INTR_SDA_DL_TIMEOUT BIT(14)
#define ASPEED_I2CD_INTR_BUS_RECOVER_DONE BIT(13)
#define ASPEED_I2CD_INTR_SLAVE_MATCH BIT(7)
#define ASPEED_I2CD_INTR_SCL_TIMEOUT BIT(6)
#define ASPEED_I2CD_INTR_ABNORMAL BIT(5)
#define ASPEED_I2CD_INTR_NORMAL_STOP BIT(4)
Expand Down Expand Up @@ -101,6 +103,9 @@
#define ASPEED_I2CD_M_TX_CMD BIT(1)
#define ASPEED_I2CD_M_START_CMD BIT(0)

/* 0x18 : I2CD Slave Device Address Register */
#define ASPEED_I2CD_DEV_ADDR_MASK GENMASK(6, 0)

enum aspeed_i2c_master_state {
ASPEED_I2C_MASTER_START,
ASPEED_I2C_MASTER_TX_FIRST,
Expand All @@ -111,6 +116,15 @@ enum aspeed_i2c_master_state {
ASPEED_I2C_MASTER_INACTIVE,
};

enum aspeed_i2c_slave_state {
ASPEED_I2C_SLAVE_START,
ASPEED_I2C_SLAVE_READ_REQUESTED,
ASPEED_I2C_SLAVE_READ_PROCESSED,
ASPEED_I2C_SLAVE_WRITE_REQUESTED,
ASPEED_I2C_SLAVE_WRITE_RECEIVED,
ASPEED_I2C_SLAVE_STOP,
};

struct aspeed_i2c_bus {
struct i2c_adapter adap;
struct device *dev;
Expand All @@ -130,6 +144,10 @@ struct aspeed_i2c_bus {
int cmd_err;
/* Protected only by i2c_lock_bus */
int master_xfer_result;
#if IS_ENABLED(CONFIG_I2C_SLAVE)
struct i2c_client *slave;
enum aspeed_i2c_slave_state slave_state;
#endif /* CONFIG_I2C_SLAVE */
};

static int aspeed_i2c_reset(struct aspeed_i2c_bus *bus);
Expand Down Expand Up @@ -202,6 +220,110 @@ static int aspeed_i2c_recover_bus(struct aspeed_i2c_bus *bus)
return aspeed_i2c_reset(bus);
}

#if IS_ENABLED(CONFIG_I2C_SLAVE)
static bool aspeed_i2c_slave_irq(struct aspeed_i2c_bus *bus)
{
u32 command, irq_status, status_ack = 0;
struct i2c_client *slave = bus->slave;
bool irq_handled = true;
u8 value;

spin_lock(&bus->lock);
if (!slave) {
irq_handled = false;
goto out;
}

command = readl(bus->base + ASPEED_I2C_CMD_REG);
irq_status = readl(bus->base + ASPEED_I2C_INTR_STS_REG);

/* Slave was requested, restart state machine. */
if (irq_status & ASPEED_I2CD_INTR_SLAVE_MATCH) {
status_ack |= ASPEED_I2CD_INTR_SLAVE_MATCH;
bus->slave_state = ASPEED_I2C_SLAVE_START;
}

/* Slave is not currently active, irq was for someone else. */
if (bus->slave_state == ASPEED_I2C_SLAVE_STOP) {
irq_handled = false;
goto out;
}

dev_dbg(bus->dev, "slave irq status 0x%08x, cmd 0x%08x\n",
irq_status, command);

/* Slave was sent something. */
if (irq_status & ASPEED_I2CD_INTR_RX_DONE) {
value = readl(bus->base + ASPEED_I2C_BYTE_BUF_REG) >> 8;
/* Handle address frame. */
if (bus->slave_state == ASPEED_I2C_SLAVE_START) {
if (value & 0x1)
bus->slave_state =
ASPEED_I2C_SLAVE_READ_REQUESTED;
else
bus->slave_state =
ASPEED_I2C_SLAVE_WRITE_REQUESTED;
}
status_ack |= ASPEED_I2CD_INTR_RX_DONE;
}

/* Slave was asked to stop. */
if (irq_status & ASPEED_I2CD_INTR_NORMAL_STOP) {
status_ack |= ASPEED_I2CD_INTR_NORMAL_STOP;
bus->slave_state = ASPEED_I2C_SLAVE_STOP;
}
if (irq_status & ASPEED_I2CD_INTR_TX_NAK) {
status_ack |= ASPEED_I2CD_INTR_TX_NAK;
bus->slave_state = ASPEED_I2C_SLAVE_STOP;
}

switch (bus->slave_state) {
case ASPEED_I2C_SLAVE_READ_REQUESTED:
if (irq_status & ASPEED_I2CD_INTR_TX_ACK)
dev_err(bus->dev, "Unexpected ACK on read request.\n");
bus->slave_state = ASPEED_I2C_SLAVE_READ_PROCESSED;

i2c_slave_event(slave, I2C_SLAVE_READ_REQUESTED, &value);
writel(value, bus->base + ASPEED_I2C_BYTE_BUF_REG);
writel(ASPEED_I2CD_S_TX_CMD, bus->base + ASPEED_I2C_CMD_REG);
break;
case ASPEED_I2C_SLAVE_READ_PROCESSED:
status_ack |= ASPEED_I2CD_INTR_TX_ACK;
if (!(irq_status & ASPEED_I2CD_INTR_TX_ACK))
dev_err(bus->dev,
"Expected ACK after processed read.\n");
i2c_slave_event(slave, I2C_SLAVE_READ_PROCESSED, &value);
writel(value, bus->base + ASPEED_I2C_BYTE_BUF_REG);
writel(ASPEED_I2CD_S_TX_CMD, bus->base + ASPEED_I2C_CMD_REG);
break;
case ASPEED_I2C_SLAVE_WRITE_REQUESTED:
bus->slave_state = ASPEED_I2C_SLAVE_WRITE_RECEIVED;
i2c_slave_event(slave, I2C_SLAVE_WRITE_REQUESTED, &value);
break;
case ASPEED_I2C_SLAVE_WRITE_RECEIVED:
i2c_slave_event(slave, I2C_SLAVE_WRITE_RECEIVED, &value);
break;
case ASPEED_I2C_SLAVE_STOP:
i2c_slave_event(slave, I2C_SLAVE_STOP, &value);
break;
default:
dev_err(bus->dev, "unhandled slave_state: %d\n",
bus->slave_state);
break;
}

if (status_ack != irq_status)
dev_err(bus->dev,
"irq handled != irq. expected %x, but was %x\n",
irq_status, status_ack);
writel(status_ack, bus->base + ASPEED_I2C_INTR_STS_REG);

out:
spin_unlock(&bus->lock);
return irq_handled;
}
#endif /* CONFIG_I2C_SLAVE */

/* precondition: bus.lock has been acquired. */
static void aspeed_i2c_do_start(struct aspeed_i2c_bus *bus)
{
Expand Down Expand Up @@ -427,6 +549,13 @@ static irqreturn_t aspeed_i2c_bus_irq(int irq, void *dev_id)
{
struct aspeed_i2c_bus *bus = dev_id;

#if IS_ENABLED(CONFIG_I2C_SLAVE)
if (aspeed_i2c_slave_irq(bus)) {
dev_dbg(bus->dev, "irq handled by slave.\n");
return IRQ_HANDLED;
}
#endif /* CONFIG_I2C_SLAVE */

return aspeed_i2c_master_irq(bus) ? IRQ_HANDLED : IRQ_NONE;
}

Expand Down Expand Up @@ -474,9 +603,75 @@ static u32 aspeed_i2c_functionality(struct i2c_adapter *adap)
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA;
}

#if IS_ENABLED(CONFIG_I2C_SLAVE)
/* precondition: bus.lock has been acquired. */
static void __aspeed_i2c_reg_slave(struct aspeed_i2c_bus *bus, u16 slave_addr)
{
u32 addr_reg_val, func_ctrl_reg_val;

/* Set slave addr. */
addr_reg_val = readl(bus->base + ASPEED_I2C_DEV_ADDR_REG);
addr_reg_val &= ~ASPEED_I2CD_DEV_ADDR_MASK;
addr_reg_val |= slave_addr & ASPEED_I2CD_DEV_ADDR_MASK;
writel(addr_reg_val, bus->base + ASPEED_I2C_DEV_ADDR_REG);

/* Turn on slave mode. */
func_ctrl_reg_val = readl(bus->base + ASPEED_I2C_FUN_CTRL_REG);
func_ctrl_reg_val |= ASPEED_I2CD_SLAVE_EN;
writel(func_ctrl_reg_val, bus->base + ASPEED_I2C_FUN_CTRL_REG);
}

static int aspeed_i2c_reg_slave(struct i2c_client *client)
{
struct aspeed_i2c_bus *bus = i2c_get_adapdata(client->adapter);
unsigned long flags;

spin_lock_irqsave(&bus->lock, flags);
if (bus->slave) {
spin_unlock_irqrestore(&bus->lock, flags);
return -EINVAL;
}

__aspeed_i2c_reg_slave(bus, client->addr);

bus->slave = client;
bus->slave_state = ASPEED_I2C_SLAVE_STOP;
spin_unlock_irqrestore(&bus->lock, flags);

return 0;
}

static int aspeed_i2c_unreg_slave(struct i2c_client *client)
{
struct aspeed_i2c_bus *bus = i2c_get_adapdata(client->adapter);
u32 func_ctrl_reg_val;
unsigned long flags;

spin_lock_irqsave(&bus->lock, flags);
if (!bus->slave) {
spin_unlock_irqrestore(&bus->lock, flags);
return -EINVAL;
}

/* Turn off slave mode. */
func_ctrl_reg_val = readl(bus->base + ASPEED_I2C_FUN_CTRL_REG);
func_ctrl_reg_val &= ~ASPEED_I2CD_SLAVE_EN;
writel(func_ctrl_reg_val, bus->base + ASPEED_I2C_FUN_CTRL_REG);

bus->slave = NULL;
spin_unlock_irqrestore(&bus->lock, flags);

return 0;
}
#endif /* CONFIG_I2C_SLAVE */

static const struct i2c_algorithm aspeed_i2c_algo = {
.master_xfer = aspeed_i2c_master_xfer,
.functionality = aspeed_i2c_functionality,
#if IS_ENABLED(CONFIG_I2C_SLAVE)
.reg_slave = aspeed_i2c_reg_slave,
.unreg_slave = aspeed_i2c_unreg_slave,
#endif /* CONFIG_I2C_SLAVE */
};

static u32 aspeed_i2c_get_clk_reg_val(u32 divisor)
Expand Down Expand Up @@ -551,6 +746,12 @@ static int aspeed_i2c_init(struct aspeed_i2c_bus *bus,
writel(readl(bus->base + ASPEED_I2C_FUN_CTRL_REG) | fun_ctrl_reg,
bus->base + ASPEED_I2C_FUN_CTRL_REG);

#if IS_ENABLED(CONFIG_I2C_SLAVE)
/* If slave has already been registered, re-enable it. */
if (bus->slave)
__aspeed_i2c_reg_slave(bus, bus->slave->addr);
#endif /* CONFIG_I2C_SLAVE */

/* Set interrupt generation of I2C controller */
writel(ASPEED_I2CD_INTR_ALL, bus->base + ASPEED_I2C_INTR_CTRL_REG);

Expand Down

0 comments on commit f9eb913

Please sign in to comment.