| #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; |
| } |
| |