[usbdev] Power on the diff receiver before using it

Add a parameter to usbdev to count time until a differential receiver
should be operational. If the device is configured to use a differential
receiver, reject detected K and J symbols for the power-on time after
enabling the receiver.

Signed-off-by: Alexander Williams <awill@google.com>
diff --git a/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_pe.sv b/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_pe.sv
index f1a7c4d..82c6070 100644
--- a/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_pe.sv
+++ b/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_pe.sv
@@ -34,6 +34,10 @@
   input  logic                   cfg_use_diff_rcvr_i, // 1: use usb_d_i from a differential receiver
   input  logic                   tx_osc_test_mode_i, // Oscillator test mode (constantly output JK)
   input  logic [NumOutEps-1:0]   data_toggle_clear_i, // Clear the data toggles for an EP
+  input  logic                   diff_rx_ok_i, // 1: received differential data symbols are valid.
+                                               // Set low if K and J symbols might be invalid, such
+                                               // as when an external differential receiver is
+                                               // powering on.
 
   ////////////////////////////
   // USB Endpoint Interface //
@@ -234,6 +238,7 @@
     .link_reset_i           (link_reset_i),
     .cfg_eop_single_bit_i   (cfg_eop_single_bit_i),
     .cfg_use_diff_rcvr_i    (cfg_use_diff_rcvr_i),
+    .diff_rx_ok_i           (diff_rx_ok_i),
     .usb_d_i                (usb_d_i),
     .usb_dp_i               (usb_dp_i),
     .usb_dn_i               (usb_dn_i),
diff --git a/hw/ip/usb_fs_nb_pe/rtl/usb_fs_rx.sv b/hw/ip/usb_fs_nb_pe/rtl/usb_fs_rx.sv
index e7916d8..b63d915 100644
--- a/hw/ip/usb_fs_nb_pe/rtl/usb_fs_rx.sv
+++ b/hw/ip/usb_fs_nb_pe/rtl/usb_fs_rx.sv
@@ -13,6 +13,7 @@
   // configuration
   input  logic cfg_eop_single_bit_i,
   input  logic cfg_use_diff_rcvr_i,
+  input  logic diff_rx_ok_i,
 
   // USB data+ and data- lines (synchronous)
   input  logic usb_d_i,
@@ -290,8 +291,12 @@
 
   always_comb begin : proc_packet_valid_d
     if (line_state_valid) begin
+      // If the differential K and J symbols are not valid, reject the
+      // containing packet as invalid.
+      if (~diff_rx_ok_i) begin
+        packet_valid_d = 0;
       // check for packet start: KJKJKK, we use the last 6 bits
-      if (!packet_valid_q && line_history_q[11:0] == 12'b011001100101) begin
+      end else if (!packet_valid_q && line_history_q[11:0] == 12'b011001100101) begin
         packet_valid_d = 1;
       end
 
@@ -341,7 +346,7 @@
   end
 
   // Used for seeing a J after the completion of resume signaling
-  assign rx_j_det_o = ~tx_en_i & (line_history_q[1:0] == 2'b10);
+  assign rx_j_det_o = diff_rx_ok_i & ~tx_en_i & (line_history_q[1:0] == 2'b10);
 
   /////////////////
   // NRZI decode //
diff --git a/hw/ip/usbdev/data/usbdev.hjson b/hw/ip/usbdev/data/usbdev.hjson
index f07f71d..93229e1 100644
--- a/hw/ip/usbdev/data/usbdev.hjson
+++ b/hw/ip/usbdev/data/usbdev.hjson
@@ -150,6 +150,13 @@
       default: "12",
       desc:    "Number of endpoints",
       local:   "true"
+    },
+    { name:    "RcvrWakeTimeUs",
+      type:    "int",
+      default: "1",
+      desc:    "Maximum number of microseconds for the differential receiver to become operational",
+      local:   "false",
+      expose:  "true"
     }
   ],
   interrupt_list: [
diff --git a/hw/ip/usbdev/rtl/usbdev.sv b/hw/ip/usbdev/rtl/usbdev.sv
index de36a30..e2ccc2b 100644
--- a/hw/ip/usbdev/rtl/usbdev.sv
+++ b/hw/ip/usbdev/rtl/usbdev.sv
@@ -11,8 +11,12 @@
 module usbdev
   import usbdev_pkg::*;
   import usbdev_reg_pkg::*;
+  import prim_util_pkg::vbits;
 #(
-  parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}}
+  parameter logic [NumAlerts-1:0] AlertAsyncOn = {NumAlerts{1'b1}},
+  parameter int RcvrWakeTimeUs = 1 // Max time (in microseconds) from rx_enable_o high to the
+                                   // external differential receiver outputting valid data (when
+                                   // configured to use one).
 ) (
   input  logic       clk_i,
   input  logic       rst_ni,
@@ -177,6 +181,27 @@
   logic usb_pwr_sense;
   logic usb_pullup_en;
 
+  //////////////////////////////////
+  // Microsecond timing reference //
+  //////////////////////////////////
+  // us_tick ticks for one cycle every us, and it is based off a free-running
+  // counter.
+  logic [5:0]   ns_cnt;
+  logic         us_tick;
+
+  assign us_tick = (ns_cnt == 6'd48);
+  always_ff @(posedge clk_usb_48mhz_i or negedge rst_usb_48mhz_ni) begin
+    if (!rst_usb_48mhz_ni) begin
+      ns_cnt <= '0;
+    end else begin
+      if (us_tick) begin
+        ns_cnt <= '0;
+      end else begin
+        ns_cnt <= ns_cnt + 1'b1;
+      end
+    end
+  end
+
   /////////////////////////////
   // Receive interface fifos //
   /////////////////////////////
@@ -269,6 +294,7 @@
   logic [NEndpoints-1:0] in_rdy_async;
   logic [3:0]            usb_out_endpoint;
   logic                  usb_out_endpoint_val;
+  logic                  usb_use_diff_rcvr, usb_diff_rx_ok;
 
   // Endpoint enables
   always_comb begin : proc_map_ep_enable
@@ -568,6 +594,9 @@
     .mem_wdata_o          (usb_mem_b_wdata),
     .mem_rdata_i          (usb_mem_b_rdata),
 
+    // time reference
+    .us_tick_i            (us_tick),
+
     // control
     .enable_i             (usb_enable),
     .devaddr_i            (usb_device_addr),
@@ -576,6 +605,7 @@
     .out_ep_enabled_i     (usb_ep_out_enable),
     .out_ep_iso_i         (ep_out_iso), // cdc ok, quasi-static
     .in_ep_iso_i          (ep_in_iso), // cdc ok, quasi-static
+    .diff_rx_ok_i         (usb_diff_rx_ok),
     .cfg_eop_single_bit_i (reg2hw.phy_config.eop_single_bit.q), // cdc ok: quasi-static
     .tx_osc_test_mode_i   (reg2hw.phy_config.tx_osc_test_mode.q), // cdc ok: quasi-static
     .cfg_use_diff_rcvr_i  (reg2hw.phy_config.use_diff_rcvr.q), // cdc ok: quasi-static
@@ -1057,11 +1087,44 @@
     .usb_suspend_i          (usb_event_link_suspend)
   );
 
+  // Differential receiver enable
+  prim_flop_2sync #(
+    .Width      (1)
+  ) usbdev_sync_rcvr_enable (
+    .clk_i  (clk_usb_48mhz_i),
+    .rst_ni (rst_usb_48mhz_ni),
+    .d_i    (reg2hw.phy_config.use_diff_rcvr.q),
+    .q_o    (usb_use_diff_rcvr)
+  );
   // enable rx only when the single-ended input is enabled and the device is
   // not suspended.
-  // TODO(#10901): This can cause undefined behavior if this module stays
-  // powered to detect resume (instead of the AON module).
-  assign usb_rx_enable_o = reg2hw.phy_config.use_diff_rcvr.q & ~usb_suspend_o;
+  assign usb_rx_enable_o = usb_use_diff_rcvr & ~usb_suspend_o;
+
+  // Symbols from the differential receiver are invalid until it has finished
+  // waking up / powering on
+  // Add 1 to the specified time to account for uncertainty in the
+  // free-running counter for us_tick.
+  localparam int RcvrWakeTimeWidth = vbits(RcvrWakeTimeUs + 1);
+  logic [RcvrWakeTimeWidth-1:0] usb_rcvr_ok_counter_d, usb_rcvr_ok_counter_q;
+
+  assign usb_diff_rx_ok = (usb_rcvr_ok_counter_q == '0);
+  always_comb begin
+    // When don't need to use a differential receiver, RX is always ready
+    usb_rcvr_ok_counter_d = '0;
+    if (usb_use_diff_rcvr & !usb_rx_enable_o) begin
+      usb_rcvr_ok_counter_d = RcvrWakeTimeUs[0 +: RcvrWakeTimeWidth] + '1;
+    end else if (us_tick && (usb_rcvr_ok_counter_q > '0)) begin
+      usb_rcvr_ok_counter_d = usb_rcvr_ok_counter_q - '1;
+    end
+  end
+
+  always_ff @(posedge clk_usb_48mhz_i or negedge rst_usb_48mhz_ni) begin
+    if (!rst_usb_48mhz_ni) begin
+      usb_rcvr_ok_counter_q <= RcvrWakeTimeUs[0 +: RcvrWakeTimeWidth] + '1;
+    end else begin
+      usb_rcvr_ok_counter_q <= usb_rcvr_ok_counter_d;
+    end
+  end
 
   /////////////////////////////////////////
   // SOF Reference for Clock Calibration //
diff --git a/hw/ip/usbdev/rtl/usbdev_usbif.sv b/hw/ip/usbdev/rtl/usbdev_usbif.sv
index a23f450..d1ef16d 100644
--- a/hw/ip/usbdev/rtl/usbdev_usbif.sv
+++ b/hw/ip/usbdev/rtl/usbdev_usbif.sv
@@ -67,6 +67,9 @@
   output logic [31:0]              mem_wdata_o,
   input  logic [31:0]              mem_rdata_i,
 
+  // time reference
+  input  logic                     us_tick_i,
+
   // control
   input  logic                     enable_i,
   input  logic [6:0]               devaddr_i,
@@ -75,6 +78,7 @@
   input  logic [NEndpoints-1:0]    out_ep_enabled_i,
   input  logic [NEndpoints-1:0]    out_ep_iso_i,
   input  logic [NEndpoints-1:0]    in_ep_iso_i,
+  input  logic                     diff_rx_ok_i, // 1: differential symbols (K/J) are valid
   input  logic                     cfg_eop_single_bit_i, // 1: detect a single SE0 bit as EOP
   input  logic                     cfg_use_diff_rcvr_i, // 1: use single-ended rx data on usb_d_i
   input  logic                     tx_osc_test_mode_i, // Oscillator test mode: constant JK output
@@ -280,6 +284,7 @@
     .cfg_use_diff_rcvr_i   (cfg_use_diff_rcvr_i),
     .tx_osc_test_mode_i    (tx_osc_test_mode_i),
     .data_toggle_clear_i   (data_toggle_clear_i),
+    .diff_rx_ok_i          (diff_rx_ok_i),
 
     .usb_d_i               (usb_d_i),
     .usb_dp_i              (usb_dp_i),
@@ -333,26 +338,9 @@
     .frame_index_o         (frame_index_raw)
   );
 
-  // us_tick ticks for one cycle every us
-  logic [5:0]   ns_cnt;
-  logic         us_tick;
-  logic         do_internal_sof;
-
-  assign us_tick = (ns_cnt == 6'd48);
-  always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
-    if (!rst_ni) begin
-      ns_cnt <= '0;
-    end else begin
-      if (us_tick) begin
-        ns_cnt <= '0;
-      end else begin
-        ns_cnt <= ns_cnt + 1'b1;
-      end
-    end
-  end
-
   // Capture frame number (host sends every 1ms)
   // Generate an internal SOF if the host's is missing.
+  logic do_internal_sof;
   logic [10:0] frame_d, frame_q;
 
   assign frame_o = frame_q;
@@ -378,7 +366,7 @@
   usbdev_linkstate u_usbdev_linkstate (
     .clk_48mhz_i           (clk_48mhz_i),
     .rst_ni                (rst_ni),
-    .us_tick_i             (us_tick),
+    .us_tick_i             (us_tick_i),
     .usb_sense_i           (usb_sense_i),
     .usb_dp_i              (usb_dp_i),
     .usb_dn_i              (usb_dn_i),
diff --git a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
index cbab91a..e02b07f 100644
--- a/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
+++ b/hw/top_earlgrey/data/autogen/top_earlgrey.gen.hjson
@@ -1112,7 +1112,18 @@
         "0"
       ]
       param_decl: {}
-      param_list: []
+      memory: {}
+      param_list:
+      [
+        {
+          name: RcvrWakeTimeUs
+          desc: Maximum number of microseconds for the differential receiver to become operational
+          type: int
+          default: "1"
+          expose: "true"
+          name_top: UsbdevRcvrWakeTimeUs
+        }
+      ]
       inter_signal_list:
       [
         {
diff --git a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
index 9fdbb12..c68f94b 100644
--- a/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
+++ b/hw/top_earlgrey/rtl/autogen/top_earlgrey.sv
@@ -26,6 +26,7 @@
   // parameters for pattgen
   // parameters for rv_timer
   // parameters for usbdev
+  parameter int UsbdevRcvrWakeTimeUs = 1,
   // parameters for otp_ctrl
   parameter OtpCtrlMemInitFile = "",
   // parameters for lc_ctrl
@@ -1368,7 +1369,8 @@
       .rst_ni (rstmgr_aon_resets.rst_sys_io_div4_n[rstmgr_pkg::Domain0Sel])
   );
   usbdev #(
-    .AlertAsyncOn(alert_handler_reg_pkg::AsyncOn[11:11])
+    .AlertAsyncOn(alert_handler_reg_pkg::AsyncOn[11:11]),
+    .RcvrWakeTimeUs(UsbdevRcvrWakeTimeUs)
   ) u_usbdev (
 
       // Input