Software APIs
usbdev_aon_pullup_test.c
1 // Copyright lowRISC contributors (OpenTitan project).
2 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
3 // SPDX-License-Identifier: Apache-2.0
4 //
5 // USB AON PULLUP test
6 //
7 // Test the driving of pull up resistor(s) on the USB, and check that the
8 // AON Wake module is able to maintain the state of the bus.
9 //
10 // 1. Check for the presence of VBUS.
11 // 2. Check the DP line is not in the desired state,
12 // ie. initially it should be Low.
13 // 3. Drive the DP line to the desired state.
14 // 4. Check the DP line has changed as intended.
15 // 5. Hand over control of the pull ups to the AON Wake module.
16 // 6. Attempt to drive the DP line to the opposite state.
17 // 7. Check that the DP line has not changed.
18 // 8. Remove attempted change to the line state.
19 // 9. Reclaim control of the pull ups from the AON Wake module.
20 // 10. Repeat steps 2 through 9 for driving DP low.
21 // [ Optionally
22 // 11. Enable pin flipping.
23 // 12. Repeat all of steps 2 through 10 for the DN line.
24 // ]
25 
26 #include <stdbool.h>
27 #include <stdint.h>
28 
29 #include "sw/device/lib/base/status.h"
35 #include "sw/device/lib/testing/pinmux_testutils.h"
36 #include "sw/device/lib/testing/test_framework/check.h"
38 
39 #define USBDEV_BASE_ADDR TOP_EARLGREY_USBDEV_BASE_ADDR
40 
41 /**
42  * Are we expecting VBUS to be low at the start of the test, ie. no connection
43  * to the physical host or the DPI model is inactive?
44  */
45 static const bool kCheckLowFirst = false;
46 
47 /**
48  * USB device handle
49  */
50 static dif_usbdev_t usbdev;
51 static dif_usbdev_buffer_pool_t buffer_pool;
52 
53 /**
54  * Pinmux handle
55  */
56 static dif_pinmux_t pinmux;
57 
58 // Set the usbdev configuration according to whether or not pin flipping is
59 // desired.
60 static status_t config_set(bool pinflip) {
61  dif_usbdev_config_t config = {
63  .use_tx_d_se0 = kDifToggleDisabled,
64  .single_bit_eop = kDifToggleDisabled,
65  .pin_flip = dif_bool_to_toggle(pinflip),
66  .clock_sync_signals = kDifToggleEnabled,
67  };
68 
69  TRY(dif_usbdev_configure(&usbdev, &buffer_pool, config));
70 
71  return OK_STATUS();
72 }
73 
74 // Wait with timeout until the VBUS/SENSE signal is in the expected state.
75 static status_t vbus_wait(bool expected, uint32_t timeout_micros) {
76  ibex_timeout_t timeout = ibex_timeout_init(timeout_micros);
77  do {
78  // Read the current state of VBUS.
79  bool vbus;
80  TRY(dif_usbdev_status_get_sense(&usbdev, &vbus));
81  if (vbus == expected) {
82  return OK_STATUS();
83  }
84  } while (!ibex_timeout_check(&timeout));
85 
86  return DEADLINE_EXCEEDED();
87 }
88 
89 // Wait with timeout for the specified USB data line to be in the expected
90 // state.
91 static status_t line_wait(bool dp, bool expected, uint32_t timeout_micros) {
92  ibex_timeout_t timeout = ibex_timeout_init(timeout_micros);
93  do {
94  // Sense the current state of the pins.
96  TRY(dif_usbdev_get_phy_pins_status(&usbdev, &status));
97 
98  if ((dp && status.rx_dp == expected) || (!dp && status.rx_dn == expected)) {
99  return OK_STATUS();
100  }
101  } while (!ibex_timeout_check(&timeout));
102 
103  return DEADLINE_EXCEEDED();
104 }
105 
106 // Ensure that the specified USB data line remains in the expected state for
107 // (most of) the given time interval.
108 static status_t line_check(bool dp, bool expected, uint32_t interval_micros) {
109  // The caveat here is that if we are connected to the host, we may
110  // observe brief deviations from the expected state because the host
111  // is trying to communicate with us.
112  ibex_timeout_t timeout = ibex_timeout_init(interval_micros);
113  uint32_t mismatched = 0u;
114  uint32_t count = 0u;
115  do {
116  // Sense the current state of the pins.
118  TRY(dif_usbdev_get_phy_pins_status(&usbdev, &status));
119 
120  mismatched += (dp ? status.rx_dp : status.rx_dn) ^ expected;
121  ++count;
122  } while (!ibex_timeout_check(&timeout));
123 
124  if ((mismatched << 3) > count) {
125  // Line not in the expected state.
126  return INTERNAL();
127  }
128 
129  return OK_STATUS();
130 }
131 
132 // Delay for the specified number of microseconds, with user reporting for
133 // appropriate targets.
134 static status_t delay(bool prompt, uint32_t timeout_micros) {
135  if (prompt) {
136  LOG_INFO("Delaying...");
137  }
138  busy_spin_micros(timeout_micros);
139 
140  return OK_STATUS();
141 }
142 
143 // Enable/disable the AON Wake module, and wait with timeout until that has
144 // been confirmed.
145 static status_t aon_wait(bool prompt, dif_toggle_t enable) {
146  // We must be sure that any alteration to the USBDEV pull up enables has
147  // propagated through the CDC synchronizer and been sampled on the lower
148  // frequency (200kHz) AON clock; allow 3 clock cycles.
149  TRY(delay(prompt, 15u));
150  TRY(dif_usbdev_set_wake_enable(&usbdev, enable));
151  // The AON Wake module operates on a 200kHz clock, so the clock period is
152  // 5us; we have CDC between USBDEV and AON Wake, but it responds within a
153  // couple of its clock cycles, so this is plenty.
154  ibex_timeout_t timeout = ibex_timeout_init(20);
155  do {
157  TRY(dif_usbdev_get_wake_status(&usbdev, &status));
158  // In the requested state yet?
159  if (status.active == dif_toggle_to_bool(enable)) {
160  return OK_STATUS();
161  }
162  } while (!ibex_timeout_check(&timeout));
163 
164  return DEADLINE_EXCEEDED();
165 }
166 
167 OTTF_DEFINE_TEST_CONFIG();
168 
169 bool test_main(void) {
170  // In simulation the DPI model connects VBUS shortly after reset and
171  // prolonged delays when asserting or deasserting pull ups are wasteful.
172  uint32_t timeout_micros = 1000u;
173  uint32_t delay_micros = 1u;
174  uint32_t hold_micros = 50u;
175  bool can_flip = true;
176  bool prompt = false;
177 
179  // FPGA platforms where user intervention may be required.
180  timeout_micros = 30 * 1000 * 1000u;
181  // A short delay here permits the activity of the host controller to be
182  // observed (eg. dmesg -w on a Linux host).
183  delay_micros = 2 * 1000 * 1000u;
184  // Duration for which we should monitor the USB signal to check that it
185  // doesn't change whilst AON Wake is holding it.
186  hold_micros = 2 * 1000;
187  // The CW310/340 board and their FPGA builds cannot raise the DN pull up
188  // because the required resistor is not mounted by default.
189  can_flip = false;
190  // Report instructions/progress to user, when driven manually.
191  prompt = true;
192  }
193 
194  // Ensure that the VBUS/SENSE signal is routed through to the usbdev.
195  CHECK_DIF_OK(dif_pinmux_init(
197  pinmux_testutils_init(&pinmux);
198  CHECK_DIF_OK(dif_pinmux_input_select(
201 
202  // DP line first (Full Speed device), and for some targets the only line that
203  // has a pull up to be tested.
204  bool dp = true;
205  do {
206  // Initialize and configure the usbdev with pin flipping set appropriately.
207  CHECK_DIF_OK(
208  dif_usbdev_init(mmio_region_from_addr(USBDEV_BASE_ADDR), &usbdev));
209  CHECK_STATUS_OK(config_set(!dp));
210 
211  // Initially the VBUS may be expected to be low; if so, ensure that this is
212  // the case.
213  if (kCheckLowFirst) {
214  if (prompt) {
215  bool vbus;
216  CHECK_DIF_OK(dif_usbdev_status_get_sense(&usbdev, &vbus));
217  if (vbus) {
218  LOG_INFO("Disconnect or power down the USB");
219  }
220  }
221 
222  CHECK_STATUS_OK(vbus_wait(false, timeout_micros));
223 
224  if (prompt) {
225  LOG_INFO("Connect or power up the USB");
226  }
227  }
228 
229  // Check for VBUS present/risen.
230  CHECK_STATUS_OK(vbus_wait(true, timeout_micros));
231 
232  // Check that the AON Wake module can maintain Dx the both Low
233  // (disconnected) and High (connected) line states.
234  bool desired = true;
235  do {
236  if (prompt) {
237  LOG_INFO("Testing AON can hold %s %s", dp ? "DP" : "DN",
238  desired ? "High" : "Low");
239  }
240  // Check the Dx line is not in the desired state.
241  CHECK_STATUS_OK(line_wait(dp, !desired, 1000u));
242 
243  // Delay a little, mostly to slow things on user-driven FPGA for
244  // observation.
245  CHECK_STATUS_OK(delay(prompt, delay_micros));
246 
247  // Assert the Dx pull up, indicating the presence of a Full Speed device.
248  CHECK_DIF_OK(dif_usbdev_interface_enable(&usbdev, desired));
249 
250  // Hand over control of the pull ups to the AON Wake module
251  CHECK_STATUS_OK(aon_wait(prompt, kDifToggleEnabled));
252 
253  // Attempt to drive the Dx pull up into the opposite state
254  if (prompt) {
255  LOG_INFO(" - Attempting to drive %s %s", dp ? "DP" : "DN",
256  desired ? "Low" : "High");
257  }
258  CHECK_DIF_OK(dif_usbdev_interface_enable(&usbdev, !desired));
259 
260  // Check that the AON Wake module has kept the pull ups in their original
261  // state.
262  CHECK_STATUS_OK(line_check(dp, desired, hold_micros));
263 
264  // Retract our efforts at changing Dx; this also should not change the
265  // line state, just the intent of USBDEV.
266  CHECK_DIF_OK(dif_usbdev_interface_enable(&usbdev, desired));
267 
268  // Reclaim control of the pull ups by disabling the AON Wake module.
269  CHECK_STATUS_OK(aon_wait(prompt, kDifToggleDisabled));
270 
271  // Check the Dx line is still in the desired state.
272  CHECK_STATUS_OK(line_wait(dp, desired, 1000u));
273  desired = !desired;
274  } while (!desired);
275 
276  // Try again with the other line.
277  dp = !dp;
278  } while (can_flip && !dp);
279 
280  return true;
281 }