master
c 251 lines 8.33 KB
Raw
1 /*
2 * QTest testcase for the PCA9555 16-bit I/O port expander
3 *
4 * Copyright (c) Meta Platforms, Inc. and affiliates.
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "qemu/osdep.h"
10 #include "hw/gpio/pca9552_regs.h"
11 #include "libqos/i2c.h"
12 #include "libqos/qgraph.h"
13
14 #define PCA9555_TEST_ADDR 0x20
15
16 /* Verify power-on reset defaults match the PCA9555 datasheet. */
17 static void test_reset_defaults(void *obj, void *data, QGuestAllocator *alloc)
18 {
19 QI2CDevice *dev = (QI2CDevice *)obj;
20
21 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
22 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
23 g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xFF);
24 g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xFF);
25 g_assert_cmphex(i2c_get8(dev, PCA9535_POLARITY0), ==, 0x00);
26 g_assert_cmphex(i2c_get8(dev, PCA9535_POLARITY1), ==, 0x00);
27 g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG0), ==, 0xFF);
28 g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF);
29 }
30
31 /*
32 * When a pin is configured as output and driven low (output=0, config=0),
33 * the input register should reflect 0 for that pin.
34 * When driven high (output=1, config=0), input should reflect 1.
35 * When configured as input (config=1), PCA5555 pull-up makes it read 1.
36 */
37 static void test_output_drives_input(void *obj, void *data,
38 QGuestAllocator *alloc)
39 {
40 QI2CDevice *dev = (QI2CDevice *)obj;
41
42 i2c_set8(dev, PCA9535_CONFIG0, 0xF0);
43 i2c_set8(dev, PCA9535_OUTPUT0, 0xFA);
44
45 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFA);
46
47 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
48
49 i2c_set8(dev, PCA9535_CONFIG0, 0x00);
50 i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
51 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
52
53 i2c_set8(dev, PCA9535_OUTPUT0, 0xFF);
54 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
55 }
56
57 /*
58 * When all pins are inputs (config=0xFF) and no external driver,
59 * PCA9555 pull-ups should make the input register read all ones.
60 * Switching a pin to output mode with output=0 should drive it low.
61 */
62 static void test_input_pullup(void *obj, void *data, QGuestAllocator *alloc)
63 {
64 QI2CDevice *dev = (QI2CDevice *)obj;
65
66 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
67 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
68
69 i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
70 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
71
72 i2c_set8(dev, PCA9535_CONFIG0, 0x00);
73 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
74 }
75
76 /*
77 * Test that both ports are independent: changing port 0 registers
78 * should not affect port 1 and vice versa.
79 */
80 static void test_port_independence(void *obj, void *data,
81 QGuestAllocator *alloc)
82 {
83 QI2CDevice *dev = (QI2CDevice *)obj;
84
85 i2c_set8(dev, PCA9535_CONFIG0, 0x00);
86 i2c_set8(dev, PCA9535_OUTPUT0, 0x00);
87
88 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
89 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
90 g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xFF);
91 g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xFF);
92
93 i2c_set8(dev, PCA9535_CONFIG1, 0x00);
94 i2c_set8(dev, PCA9535_OUTPUT1, 0xAA);
95
96 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
97 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xAA);
98 g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0x00);
99 g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0xAA);
100 }
101
102 /*
103 * Polarity inversion: reading INPUT with polarity bits set should
104 * return the XOR of the actual input state and the polarity register.
105 */
106 static void test_polarity_inversion(void *obj, void *data,
107 QGuestAllocator *alloc)
108 {
109 QI2CDevice *dev = (QI2CDevice *)obj;
110
111 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xFF);
112
113 i2c_set8(dev, PCA9535_POLARITY0, 0xFF);
114 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x00);
115
116 i2c_set8(dev, PCA9535_POLARITY0, 0x0F);
117 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xF0);
118
119 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0xFF);
120
121 i2c_set8(dev, PCA9535_POLARITY1, 0xAA);
122 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT1), ==, 0x55);
123 }
124
125 /* Polarity inversion combined with output-driven pins. */
126 static void test_polarity_with_output(void *obj, void *data,
127 QGuestAllocator *alloc)
128 {
129 QI2CDevice *dev = (QI2CDevice *)obj;
130
131 i2c_set8(dev, PCA9535_CONFIG0, 0x00);
132 i2c_set8(dev, PCA9535_OUTPUT0, 0xA5);
133
134 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0xA5);
135
136 i2c_set8(dev, PCA9535_POLARITY0, 0xFF);
137 g_assert_cmphex(i2c_get8(dev, PCA9535_INPUT0), ==, 0x5A);
138
139 g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0xA5);
140 }
141
142 /*
143 * The PCA9555 auto-increments by toggling bit 0 of the command pointer
144 * within a register pair. Reading two bytes from INPUT0 should yield
145 * INPUT0 then INPUT1.
146 */
147 static void test_auto_increment_read(void *obj, void *data,
148 QGuestAllocator *alloc)
149 {
150 QI2CDevice *dev = (QI2CDevice *)obj;
151 uint8_t buf[2];
152
153 i2c_set8(dev, PCA9535_CONFIG0, 0x00);
154 i2c_set8(dev, PCA9535_CONFIG1, 0x00);
155 i2c_set8(dev, PCA9535_OUTPUT0, 0xAA);
156 i2c_set8(dev, PCA9535_OUTPUT1, 0x55);
157
158 i2c_read_block(dev, PCA9535_INPUT0, buf, 2);
159 g_assert_cmphex(buf[0], ==, 0xAA);
160 g_assert_cmphex(buf[1], ==, 0x55);
161
162 i2c_read_block(dev, PCA9535_OUTPUT0, buf, 2);
163 g_assert_cmphex(buf[0], ==, 0xAA);
164 g_assert_cmphex(buf[1], ==, 0x55);
165 }
166
167 /*
168 * Auto-increment write: writing two data bytes after a command byte
169 * should write to port 0 then port 1 of the addressed register pair.
170 */
171 static void test_auto_increment_write(void *obj, void *data,
172 QGuestAllocator *alloc)
173 {
174 QI2CDevice *dev = (QI2CDevice *)obj;
175 uint8_t buf[2];
176
177 buf[0] = 0x12;
178 buf[1] = 0x34;
179 i2c_write_block(dev, PCA9535_OUTPUT0, buf, 2);
180
181 g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT0), ==, 0x12);
182 g_assert_cmphex(i2c_get8(dev, PCA9535_OUTPUT1), ==, 0x34);
183
184 buf[0] = 0x0F;
185 buf[1] = 0xF0;
186 i2c_write_block(dev, PCA9535_CONFIG0, buf, 2);
187
188 g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG0), ==, 0x0F);
189 g_assert_cmphex(i2c_get8(dev, PCA9535_CONFIG1), ==, 0xF0);
190 }
191
192 /*
193 * Auto-increment toggles within the pair: starting from port 1 should
194 * wrap back to port 0 (toggle bit 0).
195 */
196 static void test_auto_increment_toggle(void *obj, void *data,
197 QGuestAllocator *alloc)
198 {
199 QI2CDevice *dev = (QI2CDevice *)obj;
200 uint8_t buf[2];
201
202 i2c_set8(dev, PCA9535_OUTPUT0, 0xAA);
203 i2c_set8(dev, PCA9535_OUTPUT1, 0x55);
204
205 i2c_read_block(dev, PCA9535_OUTPUT1, buf, 2);
206 g_assert_cmphex(buf[0], ==, 0x55);
207 g_assert_cmphex(buf[1], ==, 0xAA);
208 }
209
210 /*
211 * Verify the command byte wraps at 3 bits: register addresses
212 * beyond 7 should alias to the same register (bits [2:0] only).
213 */
214 static void test_command_wrapping(void *obj, void *data, QGuestAllocator *alloc)
215 {
216 QI2CDevice *dev = (QI2CDevice *)obj;
217
218 i2c_set8(dev, PCA9535_OUTPUT0, 0x42);
219
220 g_assert_cmphex(i2c_get8(dev, 0x0A), ==, 0x42);
221 }
222
223 static void pca9555_register_nodes(void)
224 {
225 QOSGraphEdgeOptions opts = {
226 .extra_device_opts = "address=0x20"
227 };
228 add_qi2c_address(&opts, &(QI2CAddress) { PCA9555_TEST_ADDR });
229
230 qos_node_create_driver("pca9555", i2c_device_create);
231 qos_node_consumes("pca9555", "i2c-bus", &opts);
232
233 qos_add_test("reset-defaults", "pca9555", test_reset_defaults, NULL);
234 qos_add_test("output-drives-input", "pca9555", test_output_drives_input,
235 NULL);
236 qos_add_test("input-pullup", "pca9555", test_input_pullup, NULL);
237 qos_add_test("port-independence", "pca9555", test_port_independence, NULL);
238 qos_add_test("polarity-inversion", "pca9555", test_polarity_inversion,
239 NULL);
240 qos_add_test("polarity-with-output", "pca9555", test_polarity_with_output,
241 NULL);
242 qos_add_test("auto-increment-read", "pca9555", test_auto_increment_read,
243 NULL);
244 qos_add_test("auto-increment-write", "pca9555", test_auto_increment_write,
245 NULL);
246 qos_add_test("auto-increment-toggle", "pca9555", test_auto_increment_toggle,
247 NULL);
248 qos_add_test("command-wrapping", "pca9555", test_command_wrapping, NULL);
249 }
250
251 libqos_init(pca9555_register_nodes);