blob: b26067960e43625870c37931a41bb0ee746d6055 [file] [log] [blame]
#include <test/fake.h>
#include "i2c-aspeed-fake.h"
static void aspeed_i2c_fake_set_irq(struct fake_device *fd, u32 mask)
{
struct aspeed_i2c_fake *i2c_fake = fake_get_data(fd);
struct test *test = fake_get_test(fd);
mask &= i2c_fake->interrupts_active;
i2c_fake->interrupts_set |= mask;
if (i2c_fake->interrupts_set)
i2c_fake->schedule_irq(test);
}
static void aspeed_i2c_fake_write_intr_ctrl_reg(struct fake_device *fd, u32 value)
{
struct aspeed_i2c_fake *i2c_fake = fake_get_data(fd);
i2c_fake->interrupts_active = value;
}
static u32 aspeed_i2c_fake_read_intr_sts_reg(struct fake_device *fd)
{
struct aspeed_i2c_fake *i2c_fake = fake_get_data(fd);
return i2c_fake->interrupts_set;
}
static void aspeed_i2c_fake_write_intr_sts_reg(struct fake_device *fd, u32 value)
{
struct aspeed_i2c_fake *i2c_fake = fake_get_data(fd);
i2c_fake->interrupts_set &= ~value;
}
static u32 aspeed_i2c_fake_read_byte_buf_reg(struct fake_device *fd)
{
struct aspeed_i2c_fake *i2c_fake = fake_get_data(fd);
return ((u32)i2c_fake->rx_buffer) << 8;
}
static void aspeed_i2c_fake_write_byte_buf_reg(struct fake_device *fd, u32 value)
{
struct aspeed_i2c_fake *i2c_fake = fake_get_data(fd);
i2c_fake->tx_buffer = value & 0xff;
}
static u32 aspeed_i2c_fake_read_command_reg(struct fake_device *fd)
{
struct aspeed_i2c_fake *i2c_fake = fake_get_data(fd);
if (i2c_fake->sda_hung) {
return ASPEED_I2CD_BUS_BUSY_STS | ASPEED_I2CD_SCL_LINE_STS;
} else if (i2c_fake->scl_hung) {
return ASPEED_I2CD_BUS_BUSY_STS | ASPEED_I2CD_SDA_LINE_STS;
} else if (i2c_fake->busy) {
i2c_fake->busy = false;
return ASPEED_I2CD_BUS_BUSY_STS | ASPEED_I2CD_SDA_LINE_STS | ASPEED_I2CD_SCL_LINE_STS;
}
return 0;
}
static void aspeed_i2c_fake_write_command_reg(struct fake_device *fd, u32 value)
{
struct aspeed_i2c_fake *i2c_fake = fake_get_data(fd);
struct test *test = fake_get_test(fd);
if (i2c_fake->scl_hung)
goto scl_hung;
if (i2c_fake->sda_hung)
goto sda_hung;
if (value & ASPEED_I2CD_M_START_CMD) {
EXPECT_TRUE(test, i2c_fake->can_restart);
i2c_fake->current_msg = &i2c_fake->msgs[i2c_fake->msgs_count++];
i2c_fake->current_msg->buf = test_kzalloc(test, 256, GFP_KERNEL);
i2c_fake->address_active = true;
i2c_fake->can_restart = true;
}
if (value & ASPEED_I2CD_M_TX_CMD) {
ASSERT_TRUE(test, aspeed_i2c_fake_is_active(i2c_fake));
if (i2c_fake->address_active) {
i2c_fake->current_msg->addr = i2c_fake->tx_buffer >> 1;
i2c_fake->address_active = false;
} else {
i2c_fake->current_msg->buf[i2c_fake->current_msg->len++] = i2c_fake->tx_buffer;
}
aspeed_i2c_fake_set_irq(fd, ASPEED_I2CD_INTR_TX_ACK);
}
if (value & ASPEED_I2CD_M_RX_CMD) {
ASSERT_TRUE(test, aspeed_i2c_fake_is_active(i2c_fake));
i2c_fake->rx_buffer = i2c_fake->current_msg->buf[i2c_fake->current_msg->len++];
i2c_fake->can_restart = false;
aspeed_i2c_fake_set_irq(fd, ASPEED_I2CD_INTR_RX_DONE);
}
if (value & ASPEED_I2CD_M_S_RX_CMD_LAST) {
i2c_fake->can_restart = true;
}
/*
* If SCL is hung, we can sometimes recover by issuing a STOP command,
* but nothing else is likely to work.
*/
scl_hung:
if (value & ASPEED_I2CD_M_STOP_CMD) {
EXPECT_TRUE(test, i2c_fake->can_restart);
i2c_fake->current_msg = NULL;
i2c_fake->scl_hung = false;
aspeed_i2c_fake_set_irq(fd, ASPEED_I2CD_INTR_NORMAL_STOP);
}
/*
* Aspeed's recover command works by trying to toggle the SCL line a
* bunch to get the slave device to let go of SDA.
*
* We still try it if SCL is hung and issuing a STOP didn't work.
*/
sda_hung:
if (value & ASPEED_I2CD_BUS_RECOVER_CMD) {
i2c_fake->sda_hung = false;
aspeed_i2c_fake_set_irq(fd, ASPEED_I2CD_INTR_BUS_RECOVER_DONE);
}
}
static struct fake_register_map_entry aspeed_i2c_fake_register_map[] = {
FAKE_32_NOP(ASPEED_I2C_FUN_CTRL_REG),
FAKE_32_NOP(ASPEED_I2C_AC_TIMING_REG1),
FAKE_32_NOP(ASPEED_I2C_AC_TIMING_REG2),
FAKE_32_WO(ASPEED_I2C_INTR_CTRL_REG, aspeed_i2c_fake_write_intr_ctrl_reg),
FAKE_32_RW(ASPEED_I2C_INTR_STS_REG, aspeed_i2c_fake_read_intr_sts_reg, aspeed_i2c_fake_write_intr_sts_reg),
FAKE_32_RW(ASPEED_I2C_CMD_REG, aspeed_i2c_fake_read_command_reg, aspeed_i2c_fake_write_command_reg),
FAKE_32_NOP(ASPEED_I2C_DEV_ADDR_REG),
FAKE_32_RW(ASPEED_I2C_BYTE_BUF_REG, aspeed_i2c_fake_read_byte_buf_reg, aspeed_i2c_fake_write_byte_buf_reg),
{},
};
static struct fake_device_description aspeed_i2c_fake_device = {
.register_map = aspeed_i2c_fake_register_map,
};
struct aspeed_i2c_fake *aspeed_i2c_fake_init(struct test *test, aspeed_i2c_fake_schedule_irq_t schedule_irq)
{
struct aspeed_i2c_fake *i2c_fake;
i2c_fake = test_kzalloc(test, sizeof(*i2c_fake), GFP_KERNEL);
i2c_fake->can_restart = true;
i2c_fake->schedule_irq = schedule_irq;
fake_device_init(test, &aspeed_i2c_fake_device, i2c_fake);
return i2c_fake;
}