+
+static uint64_t pnv_xscom_addr(uint32_t pcba)
+{
+ return P10_XSCOM_BASE | ((uint64_t) pcba << 3);
+}
+
+static uint64_t pnv_i2c_xscom_addr(int engine, uint32_t reg)
+{
+ return pnv_xscom_addr(PNV10_XSCOM_I2CM_BASE +
+ (PNV10_XSCOM_I2CM_SIZE * engine) + reg);
+}
+
+static uint64_t pnv_i2c_xscom_read(QTestState *qts, int engine, uint32_t reg)
+{
+ return qtest_readq(qts, pnv_i2c_xscom_addr(engine, reg));
+}
+
+static void pnv_i2c_xscom_write(QTestState *qts, int engine, uint32_t reg,
+ uint64_t val)
+{
+ qtest_writeq(qts, pnv_i2c_xscom_addr(engine, reg), val);
+}
+
+/* Write len bytes from buf to i2c device with given addr and port */
+static void pnv_i2c_send(pnv_i2c_dev_t *dev, const uint8_t *buf, uint16_t len)
+{
+ int byte_num;
+ uint64_t reg64;
+
+ /* select requested port */
+ reg64 = SETFIELD(I2C_MODE_BIT_RATE_DIV, 0ull, 0x2be);
+ reg64 = SETFIELD(I2C_MODE_PORT_NUM, reg64, dev->port);
+ pnv_i2c_xscom_write(dev->qts, dev->engine, I2C_MODE_REG, reg64);
+
+ /* check status for cmd complete and bus idle */
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_EXTD_STAT_REG);
+ g_assert_cmphex(reg64 & I2C_EXTD_STAT_I2C_BUSY, ==, 0);
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_STAT_REG);
+ g_assert_cmphex(reg64 & (I2C_STAT_ANY_ERR | I2C_STAT_CMD_COMP), ==,
+ I2C_STAT_CMD_COMP);
+
+ /* Send start, with stop, with address and len bytes of data */
+ reg64 = I2C_CMD_WITH_START | I2C_CMD_WITH_ADDR | I2C_CMD_WITH_STOP;
+ reg64 = SETFIELD(I2C_CMD_DEV_ADDR, reg64, dev->addr);
+ reg64 = SETFIELD(I2C_CMD_LEN_BYTES, reg64, len);
+ pnv_i2c_xscom_write(dev->qts, dev->engine, I2C_CMD_REG, reg64);
+
+ /* check status for errors */
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_STAT_REG);
+ g_assert_cmphex(reg64 & I2C_STAT_ANY_ERR, ==, 0);
+
+ /* write data bytes to fifo register */
+ for (byte_num = 0; byte_num < len; byte_num++) {
+ reg64 = SETFIELD(I2C_FIFO, 0ull, buf[byte_num]);
+ pnv_i2c_xscom_write(dev->qts, dev->engine, I2C_FIFO_REG, reg64);
+ }
+
+ /* check status for cmd complete and bus idle */
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_EXTD_STAT_REG);
+ g_assert_cmphex(reg64 & I2C_EXTD_STAT_I2C_BUSY, ==, 0);
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_STAT_REG);
+ g_assert_cmphex(reg64 & (I2C_STAT_ANY_ERR | I2C_STAT_CMD_COMP), ==,
+ I2C_STAT_CMD_COMP);
+}
+
+/* Recieve len bytes into buf from i2c device with given addr and port */
+static void pnv_i2c_recv(pnv_i2c_dev_t *dev, uint8_t *buf, uint16_t len)
+{
+ int byte_num;
+ uint64_t reg64;
+
+ /* select requested port */
+ reg64 = SETFIELD(I2C_MODE_BIT_RATE_DIV, 0ull, 0x2be);
+ reg64 = SETFIELD(I2C_MODE_PORT_NUM, reg64, dev->port);
+ pnv_i2c_xscom_write(dev->qts, dev->engine, I2C_MODE_REG, reg64);
+
+ /* check status for cmd complete and bus idle */
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_EXTD_STAT_REG);
+ g_assert_cmphex(reg64 & I2C_EXTD_STAT_I2C_BUSY, ==, 0);
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_STAT_REG);
+ g_assert_cmphex(reg64 & (I2C_STAT_ANY_ERR | I2C_STAT_CMD_COMP), ==,
+ I2C_STAT_CMD_COMP);
+
+ /* Send start, with stop, with address and len bytes of data */
+ reg64 = I2C_CMD_WITH_START | I2C_CMD_WITH_ADDR |
+ I2C_CMD_WITH_STOP | I2C_CMD_READ_NOT_WRITE;
+ reg64 = SETFIELD(I2C_CMD_DEV_ADDR, reg64, dev->addr);
+ reg64 = SETFIELD(I2C_CMD_LEN_BYTES, reg64, len);
+ pnv_i2c_xscom_write(dev->qts, dev->engine, I2C_CMD_REG, reg64);
+
+ /* check status for errors */
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_STAT_REG);
+ g_assert_cmphex(reg64 & I2C_STAT_ANY_ERR, ==, 0);
+
+ /* Read data bytes from fifo register */
+ for (byte_num = 0; byte_num < len; byte_num++) {
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_FIFO_REG);
+ buf[byte_num] = GETFIELD(I2C_FIFO, reg64);
+ }
+
+ /* check status for cmd complete and bus idle */
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_EXTD_STAT_REG);
+ g_assert_cmphex(reg64 & I2C_EXTD_STAT_I2C_BUSY, ==, 0);
+ reg64 = pnv_i2c_xscom_read(dev->qts, dev->engine, I2C_STAT_REG);
+ g_assert_cmphex(reg64 & (I2C_STAT_ANY_ERR | I2C_STAT_CMD_COMP), ==,
+ I2C_STAT_CMD_COMP);
+}
+
+static void pnv_i2c_pca9554_default_cfg(pnv_i2c_dev_t *dev)
+{
+ uint8_t buf[2];
+
+ /* input register bits are not inverted */
+ buf[0] = PCA9554_POLARITY;
+ buf[1] = 0;
+ pnv_i2c_send(dev, buf, 2);
+
+ /* All pins are inputs */
+ buf[0] = PCA9554_CONFIG;
+ buf[1] = 0xff;
+ pnv_i2c_send(dev, buf, 2);
+
+ /* Output value for when pins are outputs */
+ buf[0] = PCA9554_OUTPUT;
+ buf[1] = 0xff;
+ pnv_i2c_send(dev, buf, 2);
+}
+
+static void pnv_i2c_pca9554_set_pin(pnv_i2c_dev_t *dev, int pin, bool high)
+{
+ uint8_t send_buf[2];
+ uint8_t recv_buf[2];
+ uint8_t mask = 0x1 << pin;
+ uint8_t new_value = ((high) ? 1 : 0) << pin;
+
+ /* read current OUTPUT value */
+ send_buf[0] = PCA9554_OUTPUT;
+ pnv_i2c_send(dev, send_buf, 1);
+ pnv_i2c_recv(dev, recv_buf, 1);
+
+ /* write new OUTPUT value */
+ send_buf[1] = (recv_buf[0] & ~mask) | new_value;
+ pnv_i2c_send(dev, send_buf, 2);
+
+ /* Update config bit for output */
+ send_buf[0] = PCA9554_CONFIG;
+ pnv_i2c_send(dev, send_buf, 1);
+ pnv_i2c_recv(dev, recv_buf, 1);
+ send_buf[1] = recv_buf[0] & ~mask;
+ pnv_i2c_send(dev, send_buf, 2);
+}
+
+static uint8_t pnv_i2c_pca9554_read_pins(pnv_i2c_dev_t *dev)
+{
+ uint8_t send_buf[1];
+ uint8_t recv_buf[1];
+ uint8_t inputs;
+ send_buf[0] = PCA9554_INPUT;
+ pnv_i2c_send(dev, send_buf, 1);
+ pnv_i2c_recv(dev, recv_buf, 1);
+ inputs = recv_buf[0];
+ return inputs;
+}
+
+static void pnv_i2c_pca9554_flip_polarity(pnv_i2c_dev_t *dev)
+{
+ uint8_t recv_buf[1];
+ uint8_t send_buf[2];
+
+ send_buf[0] = PCA9554_POLARITY;
+ pnv_i2c_send(dev, send_buf, 1);
+ pnv_i2c_recv(dev, recv_buf, 1);
+ send_buf[1] = recv_buf[0] ^ 0xff;
+ pnv_i2c_send(dev, send_buf, 2);
+}
+
+static void pnv_i2c_pca9554_default_inputs(pnv_i2c_dev_t *dev)
+{
+ uint8_t pin_values = pnv_i2c_pca9554_read_pins(dev);
+ g_assert_cmphex(pin_values, ==, 0xff);
+}
+
+/* Check that setting pin values and polarity changes inputs as expected */
+static void pnv_i2c_pca554_set_pins(pnv_i2c_dev_t *dev)
+{
+ uint8_t pin_values;
+ pnv_i2c_pca9554_set_pin(dev, 0, 0);
+ pin_values = pnv_i2c_pca9554_read_pins(dev);
+ g_assert_cmphex(pin_values, ==, 0xfe);
+ pnv_i2c_pca9554_flip_polarity(dev);
+ pin_values = pnv_i2c_pca9554_read_pins(dev);
+ g_assert_cmphex(pin_values, ==, 0x01);
+ pnv_i2c_pca9554_set_pin(dev, 2, 0);
+ pin_values = pnv_i2c_pca9554_read_pins(dev);
+ g_assert_cmphex(pin_values, ==, 0x05);
+ pnv_i2c_pca9554_flip_polarity(dev);
+ pin_values = pnv_i2c_pca9554_read_pins(dev);
+ g_assert_cmphex(pin_values, ==, 0xfa);
+ pnv_i2c_pca9554_default_cfg(dev);
+ pin_values = pnv_i2c_pca9554_read_pins(dev);
+ g_assert_cmphex(pin_values, ==, 0xff);
+}
+
+static void pnv_i2c_pca9552_default_cfg(pnv_i2c_dev_t *dev)
+{
+ uint8_t buf[2];
+ /* configure pwm/psc regs */
+ buf[0] = PCA9552_PSC0;
+ buf[1] = 0xff;
+ pnv_i2c_send(dev, buf, 2);
+ buf[0] = PCA9552_PWM0;
+ buf[1] = 0x80;
+ pnv_i2c_send(dev, buf, 2);
+ buf[0] = PCA9552_PSC1;
+ buf[1] = 0xff;
+ pnv_i2c_send(dev, buf, 2);
+ buf[0] = PCA9552_PWM1;
+ buf[1] = 0x80;
+ pnv_i2c_send(dev, buf, 2);
+
+ /* configure all pins as inputs */
+ buf[0] = PCA9552_LS0;
+ buf[1] = 0x55;
+ pnv_i2c_send(dev, buf, 2);
+ buf[0] = PCA9552_LS1;
+ buf[1] = 0x55;
+ pnv_i2c_send(dev, buf, 2);
+ buf[0] = PCA9552_LS2;
+ buf[1] = 0x55;
+ pnv_i2c_send(dev, buf, 2);
+ buf[0] = PCA9552_LS3;
+ buf[1] = 0x55;
+ pnv_i2c_send(dev, buf, 2);
+}
+
+static void pnv_i2c_pca9552_set_pin(pnv_i2c_dev_t *dev, int pin, bool high)
+{
+ uint8_t send_buf[2];
+ uint8_t recv_buf[2];
+ uint8_t reg = PCA9552_LS0 + (pin / 4);
+ uint8_t shift = (pin % 4) * 2;
+ uint8_t mask = ~(0x3 << shift);
+ uint8_t new_value = ((high) ? 1 : 0) << shift;
+
+ /* read current LSx value */
+ send_buf[0] = reg;
+ pnv_i2c_send(dev, send_buf, 1);
+ pnv_i2c_recv(dev, recv_buf, 1);
+
+ /* write new value to LSx */
+ send_buf[1] = (recv_buf[0] & mask) | new_value;
+ pnv_i2c_send(dev, send_buf, 2);
+}
+
+static uint16_t pnv_i2c_pca9552_read_pins(pnv_i2c_dev_t *dev)
+{
+ uint8_t send_buf[2];
+ uint8_t recv_buf[2];
+ uint16_t inputs;
+ send_buf[0] = PCA9552_INPUT0;
+ pnv_i2c_send(dev, send_buf, 1);
+ pnv_i2c_recv(dev, recv_buf, 1);
+ inputs = recv_buf[0];
+ send_buf[0] = PCA9552_INPUT1;
+ pnv_i2c_send(dev, send_buf, 1);
+ pnv_i2c_recv(dev, recv_buf, 1);
+ inputs |= recv_buf[0] << 8;
+ return inputs;
+}
+
+static void pnv_i2c_pca9552_default_inputs(pnv_i2c_dev_t *dev)
+{
+ uint16_t pin_values = pnv_i2c_pca9552_read_pins(dev);
+ g_assert_cmphex(pin_values, ==, 0xffff);
+}
+
+/*
+ * Set pins 0-4 one at a time and verify that pins 5-9 are
+ * set to the same value
+ */
+static void pnv_i2c_pca552_set_pins(pnv_i2c_dev_t *dev)
+{
+ uint16_t pin_values;
+
+ /* set pin 0 low */
+ pnv_i2c_pca9552_set_pin(dev, 0, 0);
+ pin_values = pnv_i2c_pca9552_read_pins(dev);
+
+ /* pins 0 and 5 should be low */
+ g_assert_cmphex(pin_values, ==, 0xffde);
+
+ /* set pin 1 low */
+ pnv_i2c_pca9552_set_pin(dev, 1, 0);
+ pin_values = pnv_i2c_pca9552_read_pins(dev);
+
+ /* pins 0, 1, 5 and 6 should be low */
+ g_assert_cmphex(pin_values, ==, 0xff9c);
+
+ /* set pin 2 low */
+ pnv_i2c_pca9552_set_pin(dev, 2, 0);
+ pin_values = pnv_i2c_pca9552_read_pins(dev);
+
+ /* pins 0, 1, 2, 5, 6 and 7 should be low */
+ g_assert_cmphex(pin_values, ==, 0xff18);
+
+ /* set pin 3 low */
+ pnv_i2c_pca9552_set_pin(dev, 3, 0);
+ pin_values = pnv_i2c_pca9552_read_pins(dev);
+
+ /* pins 0, 1, 2, 3, 5, 6, 7 and 8 should be low */
+ g_assert_cmphex(pin_values, ==, 0xfe10);
+
+ /* set pin 4 low */
+ pnv_i2c_pca9552_set_pin(dev, 4, 0);
+ pin_values = pnv_i2c_pca9552_read_pins(dev);
+
+ /* pins 0, 1, 2, 3, 5, 6, 7, 8 and 9 should be low */
+ g_assert_cmphex(pin_values, ==, 0xfc00);
+
+ /* reset all pins to the high state */
+ pnv_i2c_pca9552_default_cfg(dev);
+ pin_values = pnv_i2c_pca9552_read_pins(dev);
+
+ /* verify all pins went back to the high state */
+ g_assert_cmphex(pin_values, ==, 0xffff);
+}
+
+static void reset_engine(QTestState *qts, int engine)
+{
+ pnv_i2c_xscom_write(qts, engine, I2C_RESET_I2C_REG, 0);
+}
+
+static void check_i2cm_por_regs(QTestState *qts, const PnvChip *chip)
+{
+ int engine;
+ for (engine = 0; engine < chip->num_i2c; engine++) {
+
+ /* Check version in Extended Status Register */
+ uint64_t value = pnv_i2c_xscom_read(qts, engine, I2C_EXTD_STAT_REG);
+ g_assert_cmphex(value & I2C_EXTD_STAT_I2C_VERSION, ==, 0x1700000000);
+
+ /* Check for command complete and bus idle in Status Register */
+ value = pnv_i2c_xscom_read(qts, engine, I2C_STAT_REG);
+ g_assert_cmphex(value & (I2C_STAT_ANY_ERR | I2C_STAT_CMD_COMP),
+ ==,
+ I2C_STAT_CMD_COMP);
+ }
+}
+
+static void reset_all(QTestState *qts, const PnvChip *chip)
+{
+ int engine;
+ for (engine = 0; engine < chip->num_i2c; engine++) {
+ reset_engine(qts, engine);
+ pnv_i2c_xscom_write(qts, engine, I2C_MODE_REG, 0x02be040000000000);
+ }
+}
+
+static void test_host_i2c(const void *data)
+{
+ const PnvChip *chip = data;
+ QTestState *qts;
+ const char *machine = "powernv8";
+ pnv_i2c_dev_t pca9552;
+ pnv_i2c_dev_t pca9554;
+
+ if (chip->chip_type == PNV_CHIP_POWER9) {
+ machine = "powernv9";
+ } else if (chip->chip_type == PNV_CHIP_POWER10) {
+ machine = "powernv10-rainier";
+ }
+
+ qts = qtest_initf("-M %s -smp %d,cores=1,threads=%d -nographic "
+ "-nodefaults -serial mon:stdio -S "
+ "-d guest_errors",
+ machine, SMT, SMT);
+
+ /* Check the I2C master status registers after POR */
+ check_i2cm_por_regs(qts, chip);
+
+ /* Now do a forced "immediate" reset on all engines */
+ reset_all(qts, chip);
+
+ /* Check that the status values are still good */
+ check_i2cm_por_regs(qts, chip);
+
+ /* P9 doesn't have any i2c devices attached at this time */
+ if (chip->chip_type != PNV_CHIP_POWER10) {
+ qtest_quit(qts);
+ return;
+ }
+
+ /* Initialize for a P10 pca9552 hotplug device */
+ pca9552.qts = qts;
+ pca9552.engine = 2;
+ pca9552.port = 1;
+ pca9552.addr = 0x63;
+
+ /* Set all pca9552 pins as inputs */
+ pnv_i2c_pca9552_default_cfg(&pca9552);
+
+ /* Check that all pins of the pca9552 are high */
+ pnv_i2c_pca9552_default_inputs(&pca9552);
+
+ /* perform individual pin tests */
+ pnv_i2c_pca552_set_pins(&pca9552);
+
+ /* Initialize for a P10 pca9554 CableCard Presence detection device */
+ pca9554.qts = qts;
+ pca9554.engine = 2;
+ pca9554.port = 1;
+ pca9554.addr = 0x25;
+
+ /* Set all pca9554 pins as inputs */
+ pnv_i2c_pca9554_default_cfg(&pca9554);
+
+ /* Check that all pins of the pca9554 are high */
+ pnv_i2c_pca9554_default_inputs(&pca9554);
+
+ /* perform individual pin tests */
+ pnv_i2c_pca554_set_pins(&pca9554);
+
+ qtest_quit(qts);
+}
+
+static void add_test(const char *name, void (*test)(const void *data))
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pnv_chips); i++) {
+ char *tname = g_strdup_printf("pnv-xscom/%s/%s", name,
+ pnv_chips[i].cpu_model);
+ qtest_add_data_func(tname, &pnv_chips[i], test);
+ g_free(tname);
+ }
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ add_test("host-i2c", test_host_i2c);
+ return g_test_run();
+}