| /* |
| * QTests for Nuvoton NPCM7xx SMBus Modules. |
| * |
| * Copyright 2020 Google LLC |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License as published by the |
| * Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * for more details. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qemu/bitops.h" |
| #include "libqos/i2c.h" |
| #include "libqtest.h" |
| #include "hw/sensor/tmp105_regs.h" |
| |
| #define NR_SMBUS_DEVICES 16 |
| #define SMBUS_ADDR(x) (0xf0080000 + 0x1000 * (x)) |
| #define SMBUS_IRQ(x) (64 + (x)) |
| |
| #define EVB_DEVICE_ADDR 0x48 |
| #define INVALID_DEVICE_ADDR 0x01 |
| |
| const int evb_bus_list[] = {0, 1, 2, 6}; |
| |
| /* Offsets */ |
| enum CommonRegister { |
| OFFSET_SDA = 0x0, |
| OFFSET_ST = 0x2, |
| OFFSET_CST = 0x4, |
| OFFSET_CTL1 = 0x6, |
| OFFSET_ADDR1 = 0x8, |
| OFFSET_CTL2 = 0xa, |
| OFFSET_ADDR2 = 0xc, |
| OFFSET_CTL3 = 0xe, |
| OFFSET_CST2 = 0x18, |
| OFFSET_CST3 = 0x19, |
| }; |
| |
| enum NPCM7xxSMBusBank0Register { |
| OFFSET_ADDR3 = 0x10, |
| OFFSET_ADDR7 = 0x11, |
| OFFSET_ADDR4 = 0x12, |
| OFFSET_ADDR8 = 0x13, |
| OFFSET_ADDR5 = 0x14, |
| OFFSET_ADDR9 = 0x15, |
| OFFSET_ADDR6 = 0x16, |
| OFFSET_ADDR10 = 0x17, |
| OFFSET_CTL4 = 0x1a, |
| OFFSET_CTL5 = 0x1b, |
| OFFSET_SCLLT = 0x1c, |
| OFFSET_FIF_CTL = 0x1d, |
| OFFSET_SCLHT = 0x1e, |
| }; |
| |
| enum NPCM7xxSMBusBank1Register { |
| OFFSET_FIF_CTS = 0x10, |
| OFFSET_FAIR_PER = 0x11, |
| OFFSET_TXF_CTL = 0x12, |
| OFFSET_T_OUT = 0x14, |
| OFFSET_TXF_STS = 0x1a, |
| OFFSET_RXF_STS = 0x1c, |
| OFFSET_RXF_CTL = 0x1e, |
| }; |
| |
| /* ST fields */ |
| #define ST_STP BIT(7) |
| #define ST_SDAST BIT(6) |
| #define ST_BER BIT(5) |
| #define ST_NEGACK BIT(4) |
| #define ST_STASTR BIT(3) |
| #define ST_NMATCH BIT(2) |
| #define ST_MODE BIT(1) |
| #define ST_XMIT BIT(0) |
| |
| /* CST fields */ |
| #define CST_ARPMATCH BIT(7) |
| #define CST_MATCHAF BIT(6) |
| #define CST_TGSCL BIT(5) |
| #define CST_TSDA BIT(4) |
| #define CST_GCMATCH BIT(3) |
| #define CST_MATCH BIT(2) |
| #define CST_BB BIT(1) |
| #define CST_BUSY BIT(0) |
| |
| /* CST2 fields */ |
| #define CST2_INSTTS BIT(7) |
| #define CST2_MATCH7F BIT(6) |
| #define CST2_MATCH6F BIT(5) |
| #define CST2_MATCH5F BIT(4) |
| #define CST2_MATCH4F BIT(3) |
| #define CST2_MATCH3F BIT(2) |
| #define CST2_MATCH2F BIT(1) |
| #define CST2_MATCH1F BIT(0) |
| |
| /* CST3 fields */ |
| #define CST3_EO_BUSY BIT(7) |
| #define CST3_MATCH10F BIT(2) |
| #define CST3_MATCH9F BIT(1) |
| #define CST3_MATCH8F BIT(0) |
| |
| /* CTL1 fields */ |
| #define CTL1_STASTRE BIT(7) |
| #define CTL1_NMINTE BIT(6) |
| #define CTL1_GCMEN BIT(5) |
| #define CTL1_ACK BIT(4) |
| #define CTL1_EOBINTE BIT(3) |
| #define CTL1_INTEN BIT(2) |
| #define CTL1_STOP BIT(1) |
| #define CTL1_START BIT(0) |
| |
| /* CTL2 fields */ |
| #define CTL2_SCLFRQ(rv) extract8((rv), 1, 6) |
| #define CTL2_ENABLE BIT(0) |
| |
| /* CTL3 fields */ |
| #define CTL3_SCL_LVL BIT(7) |
| #define CTL3_SDA_LVL BIT(6) |
| #define CTL3_BNK_SEL BIT(5) |
| #define CTL3_400K_MODE BIT(4) |
| #define CTL3_IDL_START BIT(3) |
| #define CTL3_ARPMEN BIT(2) |
| #define CTL3_SCLFRQ(rv) extract8((rv), 0, 2) |
| |
| /* ADDR fields */ |
| #define ADDR_EN BIT(7) |
| #define ADDR_A(rv) extract8((rv), 0, 6) |
| |
| /* FIF_CTL fields */ |
| #define FIF_CTL_FIFO_EN BIT(4) |
| |
| /* FIF_CTS fields */ |
| #define FIF_CTS_CLR_FIFO BIT(6) |
| #define FIF_CTS_RFTE_IE BIT(3) |
| #define FIF_CTS_RXF_TXE BIT(1) |
| |
| /* TXF_CTL fields */ |
| #define TXF_CTL_THR_TXIE BIT(6) |
| #define TXF_CTL_TX_THR(rv) extract8((rv), 0, 5) |
| |
| /* TXF_STS fields */ |
| #define TXF_STS_TX_THST BIT(6) |
| #define TXF_STS_TX_BYTES(rv) extract8((rv), 0, 5) |
| |
| /* RXF_CTL fields */ |
| #define RXF_CTL_THR_RXIE BIT(6) |
| #define RXF_CTL_LAST BIT(5) |
| #define RXF_CTL_RX_THR(rv) extract8((rv), 0, 5) |
| |
| /* RXF_STS fields */ |
| #define RXF_STS_RX_THST BIT(6) |
| #define RXF_STS_RX_BYTES(rv) extract8((rv), 0, 5) |
| |
| |
| static void choose_bank(QTestState *qts, uint64_t base_addr, uint8_t bank) |
| { |
| uint8_t ctl3 = qtest_readb(qts, base_addr + OFFSET_CTL3); |
| |
| if (bank) { |
| ctl3 |= CTL3_BNK_SEL; |
| } else { |
| ctl3 &= ~CTL3_BNK_SEL; |
| } |
| |
| qtest_writeb(qts, base_addr + OFFSET_CTL3, ctl3); |
| } |
| |
| static void check_running(QTestState *qts, uint64_t base_addr) |
| { |
| g_assert_true(qtest_readb(qts, base_addr + OFFSET_CST) & CST_BUSY); |
| g_assert_true(qtest_readb(qts, base_addr + OFFSET_CST) & CST_BB); |
| } |
| |
| static void check_stopped(QTestState *qts, uint64_t base_addr) |
| { |
| uint8_t cst3; |
| |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, 0); |
| g_assert_false(qtest_readb(qts, base_addr + OFFSET_CST) & CST_BUSY); |
| g_assert_false(qtest_readb(qts, base_addr + OFFSET_CST) & CST_BB); |
| |
| cst3 = qtest_readb(qts, base_addr + OFFSET_CST3); |
| g_assert_true(cst3 & CST3_EO_BUSY); |
| qtest_writeb(qts, base_addr + OFFSET_CST3, cst3); |
| cst3 = qtest_readb(qts, base_addr + OFFSET_CST3); |
| g_assert_false(cst3 & CST3_EO_BUSY); |
| } |
| |
| static void enable_bus(QTestState *qts, uint64_t base_addr) |
| { |
| uint8_t ctl2 = qtest_readb(qts, base_addr + OFFSET_CTL2); |
| |
| ctl2 |= CTL2_ENABLE; |
| qtest_writeb(qts, base_addr + OFFSET_CTL2, ctl2); |
| g_assert_true(qtest_readb(qts, base_addr + OFFSET_CTL2) & CTL2_ENABLE); |
| } |
| |
| static void disable_bus(QTestState *qts, uint64_t base_addr) |
| { |
| uint8_t ctl2 = qtest_readb(qts, base_addr + OFFSET_CTL2); |
| |
| ctl2 &= ~CTL2_ENABLE; |
| qtest_writeb(qts, base_addr + OFFSET_CTL2, ctl2); |
| g_assert_false(qtest_readb(qts, base_addr + OFFSET_CTL2) & CTL2_ENABLE); |
| } |
| |
| static void start_transfer(QTestState *qts, uint64_t base_addr) |
| { |
| uint8_t ctl1; |
| |
| ctl1 = CTL1_START | CTL1_INTEN | CTL1_STASTRE; |
| qtest_writeb(qts, base_addr + OFFSET_CTL1, ctl1); |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_CTL1), ==, |
| CTL1_INTEN | CTL1_STASTRE | CTL1_INTEN); |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, |
| ST_MODE | ST_XMIT | ST_SDAST); |
| check_running(qts, base_addr); |
| } |
| |
| static void stop_transfer(QTestState *qts, uint64_t base_addr) |
| { |
| uint8_t ctl1 = qtest_readb(qts, base_addr + OFFSET_CTL1); |
| |
| ctl1 &= ~(CTL1_START | CTL1_ACK); |
| ctl1 |= CTL1_STOP | CTL1_INTEN | CTL1_EOBINTE; |
| qtest_writeb(qts, base_addr + OFFSET_CTL1, ctl1); |
| ctl1 = qtest_readb(qts, base_addr + OFFSET_CTL1); |
| g_assert_false(ctl1 & CTL1_STOP); |
| } |
| |
| static void send_byte(QTestState *qts, uint64_t base_addr, uint8_t byte) |
| { |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, |
| ST_MODE | ST_XMIT | ST_SDAST); |
| qtest_writeb(qts, base_addr + OFFSET_SDA, byte); |
| } |
| |
| static bool check_recv(QTestState *qts, uint64_t base_addr) |
| { |
| uint8_t st, fif_ctl, rxf_ctl, rxf_sts; |
| bool fifo; |
| |
| st = qtest_readb(qts, base_addr + OFFSET_ST); |
| choose_bank(qts, base_addr, 0); |
| fif_ctl = qtest_readb(qts, base_addr + OFFSET_FIF_CTL); |
| fifo = fif_ctl & FIF_CTL_FIFO_EN; |
| if (!fifo) { |
| return st == (ST_MODE | ST_SDAST); |
| } |
| |
| choose_bank(qts, base_addr, 1); |
| rxf_ctl = qtest_readb(qts, base_addr + OFFSET_RXF_CTL); |
| rxf_sts = qtest_readb(qts, base_addr + OFFSET_RXF_STS); |
| |
| if ((rxf_ctl & RXF_CTL_THR_RXIE) && RXF_STS_RX_BYTES(rxf_sts) < 16) { |
| return st == ST_MODE; |
| } else { |
| return st == (ST_MODE | ST_SDAST); |
| } |
| } |
| |
| static uint8_t recv_byte(QTestState *qts, uint64_t base_addr) |
| { |
| g_assert_true(check_recv(qts, base_addr)); |
| return qtest_readb(qts, base_addr + OFFSET_SDA); |
| } |
| |
| static void send_address(QTestState *qts, uint64_t base_addr, uint8_t addr, |
| bool recv, bool valid) |
| { |
| uint8_t encoded_addr = (addr << 1) | (recv ? 1 : 0); |
| uint8_t st; |
| |
| qtest_writeb(qts, base_addr + OFFSET_SDA, encoded_addr); |
| st = qtest_readb(qts, base_addr + OFFSET_ST); |
| |
| if (valid) { |
| if (recv) { |
| g_assert_cmphex(st, ==, ST_MODE | ST_SDAST | ST_STASTR); |
| } else { |
| g_assert_cmphex(st, ==, ST_MODE | ST_XMIT | ST_SDAST | ST_STASTR); |
| } |
| |
| qtest_writeb(qts, base_addr + OFFSET_ST, ST_STASTR); |
| st = qtest_readb(qts, base_addr + OFFSET_ST); |
| if (recv) { |
| g_assert_true(check_recv(qts, base_addr)); |
| } else { |
| g_assert_cmphex(st, ==, ST_MODE | ST_XMIT | ST_SDAST); |
| } |
| } else { |
| if (recv) { |
| g_assert_cmphex(st, ==, ST_MODE | ST_NEGACK); |
| } else { |
| g_assert_cmphex(st, ==, ST_MODE | ST_XMIT | ST_NEGACK); |
| } |
| } |
| } |
| |
| static void send_nack(QTestState *qts, uint64_t base_addr) |
| { |
| uint8_t ctl1 = qtest_readb(qts, base_addr + OFFSET_CTL1); |
| |
| ctl1 &= ~(CTL1_START | CTL1_STOP); |
| ctl1 |= CTL1_ACK | CTL1_INTEN; |
| qtest_writeb(qts, base_addr + OFFSET_CTL1, ctl1); |
| } |
| |
| static void start_fifo_mode(QTestState *qts, uint64_t base_addr) |
| { |
| choose_bank(qts, base_addr, 0); |
| qtest_writeb(qts, base_addr + OFFSET_FIF_CTL, FIF_CTL_FIFO_EN); |
| g_assert_true(qtest_readb(qts, base_addr + OFFSET_FIF_CTL) & |
| FIF_CTL_FIFO_EN); |
| choose_bank(qts, base_addr, 1); |
| qtest_writeb(qts, base_addr + OFFSET_FIF_CTS, |
| FIF_CTS_CLR_FIFO | FIF_CTS_RFTE_IE); |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_FIF_CTS), ==, |
| FIF_CTS_RFTE_IE); |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_TXF_STS), ==, 0); |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_RXF_STS), ==, 0); |
| } |
| |
| static void start_recv_fifo(QTestState *qts, uint64_t base_addr, uint8_t bytes) |
| { |
| choose_bank(qts, base_addr, 1); |
| qtest_writeb(qts, base_addr + OFFSET_TXF_CTL, 0); |
| qtest_writeb(qts, base_addr + OFFSET_RXF_CTL, |
| RXF_CTL_THR_RXIE | RXF_CTL_LAST | bytes); |
| } |
| |
| /* Check the SMBus's status is set correctly when disabled. */ |
| static void test_disable_bus(gconstpointer data) |
| { |
| intptr_t index = (intptr_t)data; |
| uint64_t base_addr = SMBUS_ADDR(index); |
| QTestState *qts = qtest_init("-machine npcm750-evb"); |
| |
| disable_bus(qts, base_addr); |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_CTL1), ==, 0); |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_ST), ==, 0); |
| g_assert_false(qtest_readb(qts, base_addr + OFFSET_CST3) & CST3_EO_BUSY); |
| g_assert_cmphex(qtest_readb(qts, base_addr + OFFSET_CST), ==, 0); |
| qtest_quit(qts); |
| } |
| |
| /* Check the SMBus returns a NACK for an invalid address. */ |
| static void test_invalid_addr(gconstpointer data) |
| { |
| intptr_t index = (intptr_t)data; |
| uint64_t base_addr = SMBUS_ADDR(index); |
| int irq = SMBUS_IRQ(index); |
| QTestState *qts = qtest_init("-machine npcm750-evb"); |
| |
| qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); |
| enable_bus(qts, base_addr); |
| g_assert_false(qtest_get_irq(qts, irq)); |
| start_transfer(qts, base_addr); |
| send_address(qts, base_addr, INVALID_DEVICE_ADDR, false, false); |
| g_assert_true(qtest_get_irq(qts, irq)); |
| stop_transfer(qts, base_addr); |
| check_running(qts, base_addr); |
| qtest_writeb(qts, base_addr + OFFSET_ST, ST_NEGACK); |
| g_assert_false(qtest_readb(qts, base_addr + OFFSET_ST) & ST_NEGACK); |
| check_stopped(qts, base_addr); |
| qtest_quit(qts); |
| } |
| |
| /* Check the SMBus can send and receive bytes to a device in single mode. */ |
| static void test_single_mode(gconstpointer data) |
| { |
| intptr_t index = (intptr_t)data; |
| uint64_t base_addr = SMBUS_ADDR(index); |
| int irq = SMBUS_IRQ(index); |
| uint8_t value = 0x60; |
| QTestState *qts = qtest_init("-machine npcm750-evb"); |
| |
| qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); |
| enable_bus(qts, base_addr); |
| |
| /* Sending */ |
| g_assert_false(qtest_get_irq(qts, irq)); |
| start_transfer(qts, base_addr); |
| g_assert_true(qtest_get_irq(qts, irq)); |
| send_address(qts, base_addr, EVB_DEVICE_ADDR, false, true); |
| send_byte(qts, base_addr, TMP105_REG_CONFIG); |
| send_byte(qts, base_addr, value); |
| stop_transfer(qts, base_addr); |
| check_stopped(qts, base_addr); |
| |
| /* Receiving */ |
| start_transfer(qts, base_addr); |
| send_address(qts, base_addr, EVB_DEVICE_ADDR, false, true); |
| send_byte(qts, base_addr, TMP105_REG_CONFIG); |
| start_transfer(qts, base_addr); |
| send_address(qts, base_addr, EVB_DEVICE_ADDR, true, true); |
| send_nack(qts, base_addr); |
| stop_transfer(qts, base_addr); |
| check_running(qts, base_addr); |
| g_assert_cmphex(recv_byte(qts, base_addr), ==, value); |
| check_stopped(qts, base_addr); |
| qtest_quit(qts); |
| } |
| |
| /* Check the SMBus can send and receive bytes in FIFO mode. */ |
| static void test_fifo_mode(gconstpointer data) |
| { |
| intptr_t index = (intptr_t)data; |
| uint64_t base_addr = SMBUS_ADDR(index); |
| int irq = SMBUS_IRQ(index); |
| uint8_t value = 0x60; |
| QTestState *qts = qtest_init("-machine npcm750-evb"); |
| |
| qtest_irq_intercept_in(qts, "/machine/soc/a9mpcore/gic"); |
| enable_bus(qts, base_addr); |
| start_fifo_mode(qts, base_addr); |
| g_assert_false(qtest_get_irq(qts, irq)); |
| |
| /* Sending */ |
| start_transfer(qts, base_addr); |
| send_address(qts, base_addr, EVB_DEVICE_ADDR, false, true); |
| choose_bank(qts, base_addr, 1); |
| g_assert_true(qtest_readb(qts, base_addr + OFFSET_FIF_CTS) & |
| FIF_CTS_RXF_TXE); |
| qtest_writeb(qts, base_addr + OFFSET_TXF_CTL, TXF_CTL_THR_TXIE); |
| send_byte(qts, base_addr, TMP105_REG_CONFIG); |
| send_byte(qts, base_addr, value); |
| g_assert_true(qtest_readb(qts, base_addr + OFFSET_FIF_CTS) & |
| FIF_CTS_RXF_TXE); |
| g_assert_true(qtest_readb(qts, base_addr + OFFSET_TXF_STS) & |
| TXF_STS_TX_THST); |
| g_assert_cmpuint(TXF_STS_TX_BYTES( |
| qtest_readb(qts, base_addr + OFFSET_TXF_STS)), ==, 0); |
| g_assert_true(qtest_get_irq(qts, irq)); |
| stop_transfer(qts, base_addr); |
| check_stopped(qts, base_addr); |
| |
| /* Receiving */ |
| start_fifo_mode(qts, base_addr); |
| start_transfer(qts, base_addr); |
| send_address(qts, base_addr, EVB_DEVICE_ADDR, false, true); |
| send_byte(qts, base_addr, TMP105_REG_CONFIG); |
| start_transfer(qts, base_addr); |
| qtest_writeb(qts, base_addr + OFFSET_FIF_CTS, FIF_CTS_RXF_TXE); |
| start_recv_fifo(qts, base_addr, 1); |
| send_address(qts, base_addr, EVB_DEVICE_ADDR, true, true); |
| g_assert_false(qtest_readb(qts, base_addr + OFFSET_FIF_CTS) & |
| FIF_CTS_RXF_TXE); |
| g_assert_true(qtest_readb(qts, base_addr + OFFSET_RXF_STS) & |
| RXF_STS_RX_THST); |
| g_assert_cmpuint(RXF_STS_RX_BYTES( |
| qtest_readb(qts, base_addr + OFFSET_RXF_STS)), ==, 1); |
| send_nack(qts, base_addr); |
| stop_transfer(qts, base_addr); |
| check_running(qts, base_addr); |
| g_assert_cmphex(recv_byte(qts, base_addr), ==, value); |
| g_assert_cmpuint(RXF_STS_RX_BYTES( |
| qtest_readb(qts, base_addr + OFFSET_RXF_STS)), ==, 0); |
| check_stopped(qts, base_addr); |
| qtest_quit(qts); |
| } |
| |
| static void smbus_add_test(const char *name, int index, GTestDataFunc fn) |
| { |
| g_autofree char *full_name = g_strdup_printf( |
| "npcm7xx_smbus[%d]/%s", index, name); |
| qtest_add_data_func(full_name, (void *)(intptr_t)index, fn); |
| } |
| #define add_test(name, td) smbus_add_test(#name, td, test_##name) |
| |
| int main(int argc, char **argv) |
| { |
| int i; |
| |
| g_test_init(&argc, &argv, NULL); |
| g_test_set_nonfatal_assertions(); |
| |
| for (i = 0; i < NR_SMBUS_DEVICES; ++i) { |
| add_test(disable_bus, i); |
| add_test(invalid_addr, i); |
| } |
| |
| for (i = 0; i < ARRAY_SIZE(evb_bus_list); ++i) { |
| add_test(single_mode, evb_bus_list[i]); |
| add_test(fifo_mode, evb_bus_list[i]); |
| } |
| |
| return g_test_run(); |
| } |