aboutsummaryrefslogtreecommitdiff
path: root/tests/qtest/pnv-host-i2c-test.c
blob: 7f64d597ac1d1126122a1a3e79d58404d8e487d2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
/*
 * QTest testcase for PowerNV 10 Host I2C Communications
 *
 * Copyright (c) 2023, IBM Corporation.
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or
 * later. See the COPYING file in the top-level directory.
 */
#include "qemu/osdep.h"
#include "libqtest.h"
#include "hw/gpio/pca9554_regs.h"
#include "hw/gpio/pca9552_regs.h"
#include "pnv-xscom.h"

#define PPC_BIT(bit)            (0x8000000000000000ULL >> (bit))
#define PPC_BIT32(bit)          (0x80000000 >> (bit))
#define PPC_BIT8(bit)           (0x80 >> (bit))
#define PPC_BITMASK(bs, be)     ((PPC_BIT(bs) - PPC_BIT(be)) | PPC_BIT(bs))
#define PPC_BITMASK32(bs, be)   ((PPC_BIT32(bs) - PPC_BIT32(be)) | \
                                 PPC_BIT32(bs))

#define MASK_TO_LSH(m)          (__builtin_ffsll(m) - 1)
#define GETFIELD(m, v)          (((v) & (m)) >> MASK_TO_LSH(m))
#define SETFIELD(m, v, val) \
        (((v) & ~(m)) | ((((typeof(v))(val)) << MASK_TO_LSH(m)) & (m)))

#define PNV10_XSCOM_I2CM_BASE   0xa0000
#define PNV10_XSCOM_I2CM_SIZE   0x1000

#include "hw/i2c/pnv_i2c_regs.h"

typedef struct {
    QTestState    *qts;
    const PnvChip *chip;
    int           engine;
} PnvI2cCtlr;

typedef struct {
    PnvI2cCtlr  *ctlr;
    int         port;
    uint8_t     addr;
} PnvI2cDev;


static uint64_t pnv_i2c_xscom_addr(PnvI2cCtlr *ctlr, uint32_t reg)
{
    return pnv_xscom_addr(ctlr->chip, PNV10_XSCOM_I2CM_BASE +
                          (PNV10_XSCOM_I2CM_SIZE * ctlr->engine) + reg);
}

static uint64_t pnv_i2c_xscom_read(PnvI2cCtlr *ctlr, uint32_t reg)
{
    return qtest_readq(ctlr->qts, pnv_i2c_xscom_addr(ctlr, reg));
}

static void pnv_i2c_xscom_write(PnvI2cCtlr *ctlr, uint32_t reg, uint64_t val)
{
    qtest_writeq(ctlr->qts, pnv_i2c_xscom_addr(ctlr, reg), val);
}

/* Write len bytes from buf to i2c device with given addr and port */
static void pnv_i2c_send(PnvI2cDev *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->ctlr, I2C_MODE_REG, reg64);

    /* check status for cmd complete and bus idle */
    reg64 = pnv_i2c_xscom_read(dev->ctlr, I2C_EXTD_STAT_REG);
    g_assert_cmphex(reg64 & I2C_EXTD_STAT_I2C_BUSY, ==, 0);
    reg64 = pnv_i2c_xscom_read(dev->ctlr, 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->ctlr, I2C_CMD_REG, reg64);

    /* check status for errors */
    reg64 = pnv_i2c_xscom_read(dev->ctlr, 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->ctlr, I2C_FIFO_REG, reg64);
    }

    /* check status for cmd complete and bus idle */
    reg64 = pnv_i2c_xscom_read(dev->ctlr, I2C_EXTD_STAT_REG);
    g_assert_cmphex(reg64 & I2C_EXTD_STAT_I2C_BUSY, ==, 0);
    reg64 = pnv_i2c_xscom_read(dev->ctlr, 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(PnvI2cDev *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->ctlr, I2C_MODE_REG, reg64);

    /* check status for cmd complete and bus idle */
    reg64 = pnv_i2c_xscom_read(dev->ctlr, I2C_EXTD_STAT_REG);
    g_assert_cmphex(reg64 & I2C_EXTD_STAT_I2C_BUSY, ==, 0);
    reg64 = pnv_i2c_xscom_read(dev->ctlr, 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->ctlr, I2C_CMD_REG, reg64);

    /* check status for errors */
    reg64 = pnv_i2c_xscom_read(dev->ctlr, 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->ctlr, I2C_FIFO_REG);
        buf[byte_num] = GETFIELD(I2C_FIFO, reg64);
    }

    /* check status for cmd complete and bus idle */
    reg64 = pnv_i2c_xscom_read(dev->ctlr, I2C_EXTD_STAT_REG);
    g_assert_cmphex(reg64 & I2C_EXTD_STAT_I2C_BUSY, ==, 0);
    reg64 = pnv_i2c_xscom_read(dev->ctlr, 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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cDev *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(PnvI2cCtlr *ctlr)
{
    pnv_i2c_xscom_write(ctlr, 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++) {
        PnvI2cCtlr ctlr;
        ctlr.qts = qts;
        ctlr.chip = chip;
        ctlr.engine = engine;

        /* Check version in Extended Status Register */
        uint64_t value = pnv_i2c_xscom_read(&ctlr, 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(&ctlr, 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++) {
        PnvI2cCtlr ctlr;
        ctlr.qts = qts;
        ctlr.chip = chip;
        ctlr.engine = engine;
        reset_engine(&ctlr);
        pnv_i2c_xscom_write(&ctlr, I2C_MODE_REG, 0x02be040000000000);
    }
}

static void test_host_i2c(const void *data)
{
    const PnvChip *chip = data;
    QTestState *qts;
    const char *machine = "powernv8";
    PnvI2cCtlr ctlr;
    PnvI2cDev pca9552;
    PnvI2cDev 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 */
    ctlr.qts = qts;
    ctlr.chip = chip;
    ctlr.engine = 2;
    pca9552.ctlr = &ctlr;
    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.ctlr = &ctlr;
    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();
}