master
c 223 lines 7.71 KB
Raw
1 /*
2 * QTest testcase for the PCA9554/PCA9536 I/O port expanders
3 *
4 * Copyright (c) Meta Platforms, Inc. and affiliates. (http://www.meta.com)
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "qemu/osdep.h"
10 #include "hw/gpio/pca9554_regs.h"
11 #include "libqos/i2c.h"
12 #include "libqos/qgraph.h"
13
14 #define PCA9554_TEST_ADDR 0x20
15
16 /* Verify power-on reset defaults match the PCA9554 datasheet. */
17 static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
18 {
19 QI2CDevice *dev = (QI2CDevice *)obj;
20
21 /* All pins are inputs, pulled high, with no polarity inversion. */
22 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
23 g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xFF);
24 g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x00);
25 g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0xFF);
26 }
27
28 /*
29 * A pin configured as output (config=0) drives its OUTPUT register level onto
30 * the pin (push-pull), which the INPUT register reflects. A pin configured as
31 * input (config=1) floats high through its pull-up.
32 */
33 static void test_output_drives_input(void *obj, void *data,
34 QGuestAllocator *alloc)
35 {
36 QI2CDevice *dev = (QI2CDevice *)obj;
37
38 /* Low nibble output, high nibble input (pull-up). */
39 i2c_set8(dev, PCA9554_CONFIG, 0xF0);
40 i2c_set8(dev, PCA9554_OUTPUT, 0xFA);
41 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFA);
42
43 /* All outputs, driven low then high. */
44 i2c_set8(dev, PCA9554_CONFIG, 0x00);
45 i2c_set8(dev, PCA9554_OUTPUT, 0x00);
46 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
47
48 i2c_set8(dev, PCA9554_OUTPUT, 0xFF);
49 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
50 }
51
52 /*
53 * With all pins configured as inputs the pull-ups make the INPUT register read
54 * all ones regardless of the OUTPUT register; switching a pin to output with
55 * output=0 drives it low.
56 */
57 static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc)
58 {
59 QI2CDevice *dev = (QI2CDevice *)obj;
60
61 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
62
63 i2c_set8(dev, PCA9554_OUTPUT, 0x00);
64 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
65
66 i2c_set8(dev, PCA9554_CONFIG, 0x00);
67 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
68 }
69
70 /*
71 * Polarity inversion: reading INPUT returns the XOR of the pin levels and the
72 * polarity register.
73 */
74 static void test_polarity_inversion(void *obj, void *data,
75 QGuestAllocator *alloc)
76 {
77 QI2CDevice *dev = (QI2CDevice *)obj;
78
79 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xFF);
80
81 i2c_set8(dev, PCA9554_POLARITY, 0xFF);
82 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
83
84 i2c_set8(dev, PCA9554_POLARITY, 0x0F);
85 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xF0);
86 }
87
88 /* Polarity inversion combined with output-driven pins. */
89 static void test_polarity_with_output(void *obj, void *data,
90 QGuestAllocator *alloc)
91 {
92 QI2CDevice *dev = (QI2CDevice *)obj;
93
94 i2c_set8(dev, PCA9554_CONFIG, 0x00);
95 i2c_set8(dev, PCA9554_OUTPUT, 0xA5);
96 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0xA5);
97
98 i2c_set8(dev, PCA9554_POLARITY, 0xFF);
99 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x5A);
100
101 /* Inversion only affects the INPUT read, not the OUTPUT register. */
102 g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0xA5);
103 }
104
105 /*
106 * The PCA9554 has no auto-increment: the command pointer never advances, so
107 * multi-byte reads and writes all target the addressed register.
108 */
109 static void test_no_autoincrement(void *obj, void *data,
110 QGuestAllocator *alloc)
111 {
112 QI2CDevice *dev = (QI2CDevice *)obj;
113 uint8_t buf[2];
114
115 /* Distinct values in adjacent registers. */
116 i2c_set8(dev, PCA9554_OUTPUT, 0xAA);
117 i2c_set8(dev, PCA9554_POLARITY, 0x33);
118
119 /* Two reads from OUTPUT return OUTPUT twice, not OUTPUT then POLARITY. */
120 i2c_read_block(dev, PCA9554_OUTPUT, buf, 2);
121 g_assert_cmphex(buf[0], ==, 0xAA);
122 g_assert_cmphex(buf[1], ==, 0xAA);
123
124 /* The second written byte overwrites OUTPUT; POLARITY is untouched. */
125 buf[0] = 0x12;
126 buf[1] = 0x34;
127 i2c_write_block(dev, PCA9554_OUTPUT, buf, 2);
128 g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x34);
129 g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x33);
130 }
131
132 /*
133 * The PCA9536 shares the PCA9554 register map but only has four pins, so its
134 * reset defaults and pin logic are masked to the low nibble.
135 */
136 static void test_pca9536_reset_defaults(void *obj, void *data,
137 QGuestAllocator *alloc)
138 {
139 QI2CDevice *dev = (QI2CDevice *)obj;
140
141 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0F);
142 g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x0F);
143 g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x00);
144 g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0x0F);
145 }
146
147 /* Only the four low pins are driven; the upper nibble stays low. */
148 static void test_pca9536_output_drives_input(void *obj, void *data,
149 QGuestAllocator *alloc)
150 {
151 QI2CDevice *dev = (QI2CDevice *)obj;
152
153 i2c_set8(dev, PCA9554_CONFIG, 0x00);
154
155 i2c_set8(dev, PCA9554_OUTPUT, 0x0A);
156 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0A);
157
158 i2c_set8(dev, PCA9554_OUTPUT, 0x00);
159 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x00);
160 }
161
162 /*
163 * The four upper bits address pins that do not exist on the PCA9536, so writes
164 * to the register map discard them: the writable registers read back with bits
165 * [7:4] cleared, and driving them onto the pins never surfaces in INPUT.
166 */
167 static void test_pca9536_ignores_upper_bits(void *obj, void *data,
168 QGuestAllocator *alloc)
169 {
170 QI2CDevice *dev = (QI2CDevice *)obj;
171
172 /* Bits [7:4] are dropped on write; bits [3:0] survive. */
173 i2c_set8(dev, PCA9554_OUTPUT, 0xFA);
174 g_assert_cmphex(i2c_get8(dev, PCA9554_OUTPUT), ==, 0x0A);
175
176 i2c_set8(dev, PCA9554_POLARITY, 0xF5);
177 g_assert_cmphex(i2c_get8(dev, PCA9554_POLARITY), ==, 0x05);
178
179 i2c_set8(dev, PCA9554_CONFIG, 0xF3);
180 g_assert_cmphex(i2c_get8(dev, PCA9554_CONFIG), ==, 0x03);
181
182 /*
183 * With all four pins as outputs, driving 0xFF only affects the low
184 * nibble.
185 */
186 i2c_set8(dev, PCA9554_POLARITY, 0x00);
187 i2c_set8(dev, PCA9554_CONFIG, 0x00);
188 i2c_set8(dev, PCA9554_OUTPUT, 0xFF);
189 g_assert_cmphex(i2c_get8(dev, PCA9554_INPUT), ==, 0x0F);
190 }
191
192 static void pca9554_register_nodes(void)
193 {
194 QOSGraphEdgeOptions opts = {
195 .extra_device_opts = "address=0x20"
196 };
197 add_qi2c_address(&opts, &(QI2CAddress) { PCA9554_TEST_ADDR });
198
199 qos_node_create_driver("pca9554", i2c_device_create);
200 qos_node_consumes("pca9554", "i2c-bus", &opts);
201
202 qos_add_test("reset-defaults", "pca9554", test_reset_defaults, NULL);
203 qos_add_test("output-drives-input", "pca9554", test_output_drives_input,
204 NULL);
205 qos_add_test("input-pullup", "pca9554", test_input_pullup, NULL);
206 qos_add_test("polarity-inversion", "pca9554", test_polarity_inversion,
207 NULL);
208 qos_add_test("polarity-with-output", "pca9554", test_polarity_with_output,
209 NULL);
210 qos_add_test("no-autoincrement", "pca9554", test_no_autoincrement, NULL);
211
212 qos_node_create_driver("pca9536", i2c_device_create);
213 qos_node_consumes("pca9536", "i2c-bus", &opts);
214
215 qos_add_test("reset-defaults", "pca9536", test_pca9536_reset_defaults,
216 NULL);
217 qos_add_test("output-drives-input", "pca9536",
218 test_pca9536_output_drives_input, NULL);
219 qos_add_test("ignores-upper-bits", "pca9536",
220 test_pca9536_ignores_upper_bits, NULL);
221 }
222
223 libqos_init(pca9554_register_nodes);