[usbdev] Add link state to handle resume to active

The new LinkResuming state gives a place for the FSM to park until it
sees a symbol that isn't part of the resume signaling. It's only after
seeing the end of resume that the module should begin tracking SOF.

Add the ability for software to direct the link state FSM to jump to
LinkResuming from LinkPowered. This enables software to recover an
active link state after going to deep sleep.

Convert the usbctrl wake_config regs to use the async attribute.

Prevent protocol engine from responding to transactions unless the link
is in one of the active states.

Signed-off-by: Alexander Williams <awill@google.com>
diff --git a/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_in_pe.sv b/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_in_pe.sv
index a2b8562..a165869 100644
--- a/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_in_pe.sv
+++ b/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_in_pe.sv
@@ -27,6 +27,7 @@
   input  logic               clk_48mhz_i,
   input  logic               rst_ni,
   input  logic               link_reset_i,
+  input  logic               link_active_i,
   input  logic [6:0]         dev_addr_i,
 
 
@@ -284,7 +285,7 @@
     if (!rst_ni) begin
       in_xact_state <= StIdle;
       in_ep_rollback_o <= 1'b0;
-    end else if (link_reset_i) begin
+    end else if (link_reset_i || !link_active_i) begin
       in_xact_state <= StIdle;
       in_ep_rollback_o <= 1'b0;
     end else begin
diff --git a/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_out_pe.sv b/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_out_pe.sv
index d868bab..bb03fa3 100644
--- a/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_out_pe.sv
+++ b/hw/ip/usb_fs_nb_pe/rtl/usb_fs_nb_out_pe.sv
@@ -28,6 +28,7 @@
   input  logic                   clk_48mhz_i,
   input  logic                   rst_ni,
   input  logic                   link_reset_i,
+  input  logic                   link_active_i,
   input  logic [6:0]             dev_addr_i,
 
   ////////////////////////
@@ -350,7 +351,7 @@
   always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
     if (!rst_ni) begin
       data_toggle_q <= '0; // All endpoints
-    end else if (link_reset_i) begin
+    end else if (link_reset_i || !link_active_i) begin
       data_toggle_q <= '0; // All endpoints
     end else begin
       data_toggle_q <= data_toggle_d;
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 78bfb13..9f128a5 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
@@ -23,6 +23,8 @@
   input  logic                   clk_48mhz_i,
   input  logic                   rst_ni,        // Async. reset, active low
   input  logic                   link_reset_i,  // USB reset, sync to 48 MHz, active high
+  input  logic                   link_active_i, // Device is in Default/Addressed/Configured state
+                                                // and may respond to transactions
   input  logic [6:0]             dev_addr_i,
 
   input  logic                   cfg_eop_single_bit_i, // 1: detect a single SE0 bit as EOP
@@ -72,6 +74,7 @@
 
   // RX line status
   output logic                   rx_jjj_det_o,
+  output logic                   rx_j_det_o,
 
   // RX errors
   output logic                   rx_crc_err_o,
@@ -135,6 +138,7 @@
     .clk_48mhz_i           (clk_48mhz_i),
     .rst_ni                (rst_ni),
     .link_reset_i          (link_reset_i),
+    .link_active_i         (link_active_i),
     .dev_addr_i            (dev_addr_i),
 
     // endpoint interface
@@ -177,6 +181,7 @@
     .clk_48mhz_i            (clk_48mhz_i),
     .rst_ni                 (rst_ni),
     .link_reset_i           (link_reset_i),
+    .link_active_i          (link_active_i),
     .dev_addr_i             (dev_addr_i),
 
     // endpoint interface
@@ -233,6 +238,7 @@
     .rx_data_o              (rx_data),
     .valid_packet_o         (rx_pkt_valid),
     .rx_jjj_det_o           (rx_jjj_det_o),
+    .rx_j_det_o             (rx_j_det_o),
     .crc_error_o            (rx_crc_err_o),
     .pid_error_o            (rx_pid_err_o),
     .bitstuff_error_o       (rx_bitstuff_err_o)
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 6a1a9c8..374ff7a 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
@@ -47,6 +47,7 @@
 
   // line status for the status detection (actual rx bits after clock recovery)
   output logic rx_jjj_det_o,
+  output logic rx_j_det_o,
 
   // Error detection
   output logic crc_error_o,
@@ -319,6 +320,9 @@
   // mask out jjj detection when transmitting (because rx is forced to J)
   assign rx_jjj_det_o = ~tx_en_i & (line_history_q[5:0] == 6'b101010); // three Js
 
+  // 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);
+
   /////////////////
   // NRZI decode //
   /////////////////
diff --git a/hw/ip/usbdev/data/usbdev.hjson b/hw/ip/usbdev/data/usbdev.hjson
index cd6319f..d0c0e21 100644
--- a/hw/ip/usbdev/data/usbdev.hjson
+++ b/hw/ip/usbdev/data/usbdev.hjson
@@ -212,17 +212,29 @@
       desc: "USB Control",
       swaccess: "rw",
       hwaccess: "hro",
+      hwqe: "true",
+      async: "clk_usb_48mhz_i",
       fields: [
         {
           bits: "0",
           name: "enable",
           desc: '''
-                Set to enable the USB interface and assert the pullup.
+                Set to connect the USB interface (i.e. assert the pullup).
                 '''
           tags: [// Prevent usb from being enabled to avoid other unforeseen side effects.
                  "excl:CsrNonInitTests:CsrExclWrite"]
         }
         {
+          bits: "1",
+          name: "resume_link_active",
+          swaccess: "wo"
+          desc: '''
+                Write a 1 to this bit to instruct usbdev to jump to the LinkResuming state.
+                The write will only have an effect when the device is in the LinkPowered state.
+                Its intention is to handle a resume-from-suspend event after the IP has been powered down.
+                '''
+        }
+        {
           bits: "22:16",
           name: "device_address",
           hwaccess: "hrw",
@@ -309,15 +321,15 @@
                 '''
           enum: [
             { value: "0",
-              name: "disconnect",
-              desc: "Link disconnected (no VBUS)"
+              name: "disconnected",
+              desc: "Link disconnected (no VBUS or no pull-up connected)"
             },
             { value: "1",
               name: "powered",
-              desc: "Link powered, but not reset yet"
+              desc: "Link powered and connected, but not reset yet"
             },
             { value: "2",
-              name: "powered_suspend",
+              name: "powered_suspended",
               desc: "Link suspended (constant idle/J for > 3 ms), but not reset yet"
             },
             { value: "3",
@@ -325,13 +337,17 @@
               desc: "Link active"
             },
             { value: "4",
-              name: "suspend",
+              name: "suspended",
               desc: "Link suspended (constant idle for > 3 ms), was active before becoming suspended"
             },
             { value: "5",
               name: "active_nosof",
               desc: "Link active but no SOF has been received since the last reset."
             },
+	    { value: "6",
+	      name: "resuming",
+	      desc: "Link resuming to an active state, pending the end of resume signaling"
+	    },
           ]
         }
         {
@@ -843,6 +859,8 @@
       desc: "USB wake configuration for suspend / resume",
       swaccess: "rw",
       hwaccess: "hro",
+      async: "clk_aon_i",
+      hwqe: "true",
       fields: [
         {
           bits: "0",
@@ -862,6 +880,8 @@
           bits: "1",
           resval: "0",
           name: "wake_ack",
+          swaccess: "wo",
+          hwaccess: "hro",
           desc: '''
                 Wake acknowledgement. Once the usb device resumes from suspend, this acknowledgement is used
                 to transition the module back to normal operation.
diff --git a/hw/ip/usbdev/doc/_index.md b/hw/ip/usbdev/doc/_index.md
index e5a1bb6..dcf568c 100644
--- a/hw/ip/usbdev/doc/_index.md
+++ b/hw/ip/usbdev/doc/_index.md
@@ -193,18 +193,20 @@
 
 |State| Description |
 |-----|-------------|
-|Disconnect | The link is disconnected. This is signaled when the VBUS is not driven by the host, which results in the sense input pin being low. An interrupt is raised on entering this state.|
-|Powered| The device has been powered as VBUS is being driven by the host, but has not been reset yet. The link is reset whenever the D+ and D- are both low (an SE0 condition) for an extended period. The host will assert reset for a minimum of 10 ms, but the USB specification allows the device to detect and respond to a reset after 2.5 us. The implementation here will report the reset state and raise an interrupt when the link is in SE0 for 3 us.|
-|Powered Suspend| The link is suspended when at idle (a J condition) for more than 3 ms. An interrupt is generated when the suspend is detected and a resume interrupt is generated when the link exits the suspend state. This state is entered, if the device has not been reset yet.|
+|Disconnected | The link is disconnected. This is signaled when the VBUS is not driven by the host, which results in the sense input pin being low, or when the user has not connected the pull-up by enabling the interface. An interrupt is raised on entering this state.|
+|Powered| The device has been powered as VBUS is being driven by the host and the user has connected the pull-up, but the device has not been reset yet. The link is reset whenever the D+ and D- are both low (an SE0 condition) for an extended period. The host will assert reset for a minimum of 10 ms, but the USB specification allows the device to detect and respond to a reset after 2.5 us. The implementation here will report the reset state and raise an interrupt when the link is in SE0 for 3 us.|
+|Powered Suspended| The link is suspended when at idle (a J condition) for more than 3 ms. An interrupt is generated when the suspend is detected and a resume interrupt is generated when the link exits the suspend state. This state is entered, if the device has not been reset yet.|
+|Active No SOF| The link has been reset and can begin receiving packets, but no Start-of-Frame packets have yet been seen.|
 |Active| The link is active when it is running normally. |
-|Suspend| Similar to 'Powered Suspend', but the device was in the active state before being suspended.|
+|Suspended| Similar to 'Powered Suspended', but the device was in the active state before being suspended.|
+|Resuming| The link is awaiting the end of resume signaling before transitioning to the Active No SOF state.|
 
 |Link Events| Description |
 |-----------|-------------|
 |Disconnect| VBUS has been lost. |
 |Link Reset| The link has been in the SE0 state for 3 us.|
 |Link Suspend| The link has been in the J state for more than 3 ms, upon which we have to enter the Suspend state.|
-|Link Resume| The link has been driven to a non-J state after being in Suspend.|
+|Link Resume| The link has been driven to a non-J state after being in Suspend. For the case of resuming to active link states, the end of resume signaling has occurred.|
 |Host Lost| Signaled using an interrupt if the link is active but a start of frame (SOF) packet has not been received from the host in 4.096 ms. The host is required to send a SOF packet every 1 ms. This is not an expected condition.|
 
 
diff --git a/hw/ip/usbdev/rtl/usbdev.sv b/hw/ip/usbdev/rtl/usbdev.sv
index 48ae5a4..9239ea1 100644
--- a/hw/ip/usbdev/rtl/usbdev.sv
+++ b/hw/ip/usbdev/rtl/usbdev.sv
@@ -159,6 +159,7 @@
   logic [2:0]        usb_link_state;
   logic              usb_enable;
   logic [6:0]        usb_device_addr;
+  logic              usb_resume_link_active;
 
   logic                  data_toggle_clear_qe;
   logic                  usb_data_toggle_clear_en;
@@ -584,6 +585,7 @@
     .tx_osc_test_mode_i   (reg2hw.phy_config.tx_osc_test_mode.q), // cdc ok: quasi-static
     .cfg_rx_differential_i (reg2hw.phy_config.rx_differential_mode.q), // cdc ok: quasi-static
     .data_toggle_clear_i  (usb_data_toggle_clear),
+    .resume_link_active_i (usb_resume_link_active),
 
     // status
     .frame_o              (usb_frame),
@@ -617,15 +619,10 @@
     .q_o    ({hw2reg.usbstat.link_state.d, hw2reg.usbstat.frame.d})
   );
 
-  // sys clk -> USB clk
-  prim_flop_2sync #(
-    .Width      (1+7)
-  ) cdc_sys_to_usb (
-    .clk_i  (clk_usb_48mhz_i),
-    .rst_ni (rst_usb_48mhz_ni),
-    .d_i    ({reg2hw.usbctrl.enable.q, reg2hw.usbctrl.device_address.q}),
-    .q_o    ({usb_enable,              usb_device_addr})
-  );
+  assign usb_enable = reg2hw.usbctrl.enable.q;
+  assign usb_device_addr = reg2hw.usbctrl.device_address.q;
+  assign usb_resume_link_active = reg2hw.usbctrl.resume_link_active.qe &
+                                  reg2hw.usbctrl.resume_link_active.q;
 
   // CDC for event signals (arguably they are there for a long time so would be ok)
   // Just want a pulse to ensure only one interrupt for an event
@@ -651,14 +648,7 @@
   assign hw2reg.usbstat.host_lost.d = event_host_lost;
 
   // resets etc cause the device address to clear
-  prim_pulse_sync usbdev_devclr (
-    .clk_src_i   (clk_usb_48mhz_i),
-    .clk_dst_i   (clk_i),
-    .rst_src_ni  (rst_usb_48mhz_ni),
-    .rst_dst_ni  (rst_ni),
-    .src_pulse_i (usb_clr_devaddr),
-    .dst_pulse_o (hw2reg.usbctrl.device_address.de)
-  );
+  assign hw2reg.usbctrl.device_address.de = usb_clr_devaddr;
   assign hw2reg.usbctrl.device_address.d = '0;
 
   // AV empty is a single pulse so needs pulsesync
@@ -766,6 +756,8 @@
     .rst_ni,
     .clk_aon_i,
     .rst_aon_ni,
+    .clk_usb_48mhz_i,
+    .rst_usb_48mhz_ni,
 
     .tl_i (tl_i),
     .tl_o (tl_o),
@@ -1142,7 +1134,8 @@
   end
 
   assign usb_aon_wake_en_o = reg2hw.wake_config.wake_en.q;
-  assign usb_aon_wake_ack_o = reg2hw.wake_config.wake_ack.q;
+  assign usb_aon_wake_ack_o = reg2hw.wake_config.wake_ack.qe &
+                              reg2hw.wake_config.wake_ack.q;
   // re-use I/O version to allow software override if needed
   assign usb_suspend_o = cio_suspend_o;
 
diff --git a/hw/ip/usbdev/rtl/usbdev_linkstate.sv b/hw/ip/usbdev/rtl/usbdev_linkstate.sv
index 43196db..ebc8098 100644
--- a/hw/ip/usbdev/rtl/usbdev_linkstate.sv
+++ b/hw/ip/usbdev/rtl/usbdev_linkstate.sv
@@ -13,8 +13,11 @@
   input  logic usb_dp_i,
   input  logic usb_dn_i,
   input  logic usb_oe_i,
+  input  logic usb_pullup_en_i,
   input  logic rx_jjj_det_i,
+  input  logic rx_j_det_i,
   input  logic sof_valid_i,
+  input  logic resume_link_active_i, // pulse
 
   output logic link_disconnect_o,  // level
   output logic link_connect_o,     // level
@@ -35,15 +38,16 @@
   localparam logic [2:0]  RESET_TIMEOUT   = 3'd3;
 
   typedef enum logic [2:0] {
-    // Unpowered state
-    LinkDisconnect = 0,
-    // Powered states
+    // No power and/or no pull-up connected state
+    LinkDisconnected = 0,
+    // Powered / connected states
     LinkPowered = 1,
-    LinkPoweredSuspend = 2,
+    LinkPoweredSuspended = 2,
     // Active states
-    LinkActive = 3,
     LinkActiveNoSOF = 5,
-    LinkSuspend = 4
+    LinkActive = 3,
+    LinkSuspended = 4,
+    LinkResuming = 6
   } link_state_e;
 
   typedef enum logic [1:0] {
@@ -78,10 +82,10 @@
   // Events that are triggered by timeout
   logic ev_bus_inactive, ev_reset;
 
-  assign link_disconnect_o = (link_state_q == LinkDisconnect);
-  assign link_connect_o    = (link_state_q != LinkDisconnect);
-  assign link_suspend_o    = (link_state_q == LinkSuspend ||
-    link_state_q == LinkPoweredSuspend);
+  assign link_disconnect_o = (link_state_q == LinkDisconnected);
+  assign link_connect_o    = (link_state_q != LinkDisconnected);
+  assign link_suspend_o    = (link_state_q == LinkSuspended ||
+    link_state_q == LinkPoweredSuspended);
   assign link_active_o     = (link_state_q == LinkActive) ||
     (link_state_q == LinkActiveNoSOF);
   // Link state is stable, so we can output it to the register
@@ -128,14 +132,15 @@
     link_state_d = link_state_q;
     link_resume_o = 0;
 
-    // If VBUS ever goes away the link has disconnected
-    if (!see_pwr_sense) begin
-      link_state_d = LinkDisconnect;
+    // If VBUS ever goes away the link has disconnected (likewise if the
+    // pull-up goes away / user requested disconnection)
+    if (!see_pwr_sense || !usb_pullup_en_i) begin
+      link_state_d = LinkDisconnected;
     end else begin
       unique case (link_state_q)
         // No USB supply detected (USB spec: Attached)
-        LinkDisconnect: begin
-          if (see_pwr_sense) begin
+        LinkDisconnected: begin
+          if (see_pwr_sense & usb_pullup_en_i) begin
             link_state_d = LinkPowered;
           end
         end
@@ -143,12 +148,16 @@
         LinkPowered: begin
           if (ev_reset) begin
             link_state_d = LinkActiveNoSOF;
+          end else if (resume_link_active_i) begin
+            // Software-directed jump to resume from LinkSuspended, in case
+            // this module was previously powered down.
+            link_state_d = LinkResuming;
           end else if (ev_bus_inactive) begin
-            link_state_d = LinkPoweredSuspend;
+            link_state_d = LinkPoweredSuspended;
           end
         end
 
-        LinkPoweredSuspend: begin
+        LinkPoweredSuspended: begin
           if (ev_reset) begin
             link_state_d = LinkActiveNoSOF;
           end else if (ev_bus_active) begin
@@ -157,13 +166,27 @@
           end
         end
 
+        // An event occurred that brought the link out of LinkSuspended, but
+        // the end-of-resume signaling may not have occurred yet.
+        // Park here before starting to count towards not seeing SOF. Wait for
+        // the end of resume signaling before expecting SOF. The host will
+        // return the link to idle after a low-speed EOP. Instead of trying to
+        // capture the termination of resume signaling direclty, wait for
+        // any J / idle symbol (or a bus reset).
+        LinkResuming: begin
+          if (rx_j_det_i | ev_reset) begin
+            link_resume_o = 1;
+            link_state_d = LinkActiveNoSOF;
+          end
+        end
+
         // Active but not yet seen a frame
         // One reason for getting stuck here is the host thinks it is a LS link
         // which could happen if the flipped bit does not match the actual pins
         // Annother is the SI is bad so good data is not recovered from the link
         LinkActiveNoSOF: begin
           if (ev_bus_inactive) begin
-            link_state_d = LinkSuspend;
+            link_state_d = LinkSuspended;
           end else if (sof_valid_i) begin
             link_state_d = LinkActive;
           end
@@ -172,35 +195,34 @@
         // Active (USB spec: Default / Address / Configured)
         LinkActive: begin
           if (ev_bus_inactive) begin
-            link_state_d = LinkSuspend;
+            link_state_d = LinkSuspended;
           end else if (ev_reset) begin
             link_state_d = LinkActiveNoSOF;
           end
         end
 
-        LinkSuspend: begin
+        LinkSuspended: begin
           if (ev_reset) begin
             link_resume_o = 1;
             link_state_d  = LinkActiveNoSOF;
           end else if (ev_bus_active) begin
-            link_resume_o = 1;
-            link_state_d  = LinkActive;
+            link_state_d  = LinkResuming;
           end
         end
 
         default: begin
-          link_state_d = LinkDisconnect;
+          link_state_d = LinkDisconnected;
         end
       endcase // case (link_state_q)
     end
   end
 
-  `ASSERT(LinkStateValid_A, link_state_q inside {LinkDisconnect, LinkPowered, LinkPoweredSuspend,
-    LinkActiveNoSOF, LinkActive, LinkSuspend}, clk_48mhz_i)
+  `ASSERT(LinkStateValid_A, link_state_q inside {LinkDisconnected, LinkPowered,
+    LinkPoweredSuspended, LinkResuming, LinkActiveNoSOF, LinkActive, LinkSuspended}, clk_48mhz_i)
 
   always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin
     if (!rst_ni) begin
-      link_state_q <= LinkDisconnect;
+      link_state_q <= LinkDisconnected;
     end else begin
       link_state_q <= link_state_d;
     end
diff --git a/hw/ip/usbdev/rtl/usbdev_reg_pkg.sv b/hw/ip/usbdev/rtl/usbdev_reg_pkg.sv
index 5fe06ca..1cce7fd 100644
--- a/hw/ip/usbdev/rtl/usbdev_reg_pkg.sv
+++ b/hw/ip/usbdev/rtl/usbdev_reg_pkg.sv
@@ -204,9 +204,15 @@
   typedef struct packed {
     struct packed {
       logic        q;
+      logic        qe;
     } enable;
     struct packed {
+      logic        q;
+      logic        qe;
+    } resume_link_active;
+    struct packed {
       logic [6:0]  q;
+      logic        qe;
     } device_address;
   } usbdev_reg2hw_usbctrl_reg_t;
 
@@ -339,9 +345,11 @@
   typedef struct packed {
     struct packed {
       logic        q;
+      logic        qe;
     } wake_en;
     struct packed {
       logic        q;
+      logic        qe;
     } wake_ack;
   } usbdev_reg2hw_wake_config_reg_t;
 
@@ -541,25 +549,25 @@
 
   // Register -> HW type
   typedef struct packed {
-    usbdev_reg2hw_intr_state_reg_t intr_state; // [398:382]
-    usbdev_reg2hw_intr_enable_reg_t intr_enable; // [381:365]
-    usbdev_reg2hw_intr_test_reg_t intr_test; // [364:331]
-    usbdev_reg2hw_alert_test_reg_t alert_test; // [330:329]
-    usbdev_reg2hw_usbctrl_reg_t usbctrl; // [328:321]
-    usbdev_reg2hw_ep_out_enable_mreg_t [11:0] ep_out_enable; // [320:309]
-    usbdev_reg2hw_ep_in_enable_mreg_t [11:0] ep_in_enable; // [308:297]
-    usbdev_reg2hw_avbuffer_reg_t avbuffer; // [296:291]
-    usbdev_reg2hw_rxfifo_reg_t rxfifo; // [290:270]
-    usbdev_reg2hw_rxenable_setup_mreg_t [11:0] rxenable_setup; // [269:258]
-    usbdev_reg2hw_rxenable_out_mreg_t [11:0] rxenable_out; // [257:246]
-    usbdev_reg2hw_out_stall_mreg_t [11:0] out_stall; // [245:234]
-    usbdev_reg2hw_in_stall_mreg_t [11:0] in_stall; // [233:222]
-    usbdev_reg2hw_configin_mreg_t [11:0] configin; // [221:54]
-    usbdev_reg2hw_iso_mreg_t [11:0] iso; // [53:42]
-    usbdev_reg2hw_data_toggle_clear_mreg_t [11:0] data_toggle_clear; // [41:18]
-    usbdev_reg2hw_phy_pins_drive_reg_t phy_pins_drive; // [17:8]
-    usbdev_reg2hw_phy_config_reg_t phy_config; // [7:2]
-    usbdev_reg2hw_wake_config_reg_t wake_config; // [1:0]
+    usbdev_reg2hw_intr_state_reg_t intr_state; // [404:388]
+    usbdev_reg2hw_intr_enable_reg_t intr_enable; // [387:371]
+    usbdev_reg2hw_intr_test_reg_t intr_test; // [370:337]
+    usbdev_reg2hw_alert_test_reg_t alert_test; // [336:335]
+    usbdev_reg2hw_usbctrl_reg_t usbctrl; // [334:323]
+    usbdev_reg2hw_ep_out_enable_mreg_t [11:0] ep_out_enable; // [322:311]
+    usbdev_reg2hw_ep_in_enable_mreg_t [11:0] ep_in_enable; // [310:299]
+    usbdev_reg2hw_avbuffer_reg_t avbuffer; // [298:293]
+    usbdev_reg2hw_rxfifo_reg_t rxfifo; // [292:272]
+    usbdev_reg2hw_rxenable_setup_mreg_t [11:0] rxenable_setup; // [271:260]
+    usbdev_reg2hw_rxenable_out_mreg_t [11:0] rxenable_out; // [259:248]
+    usbdev_reg2hw_out_stall_mreg_t [11:0] out_stall; // [247:236]
+    usbdev_reg2hw_in_stall_mreg_t [11:0] in_stall; // [235:224]
+    usbdev_reg2hw_configin_mreg_t [11:0] configin; // [223:56]
+    usbdev_reg2hw_iso_mreg_t [11:0] iso; // [55:44]
+    usbdev_reg2hw_data_toggle_clear_mreg_t [11:0] data_toggle_clear; // [43:20]
+    usbdev_reg2hw_phy_pins_drive_reg_t phy_pins_drive; // [19:10]
+    usbdev_reg2hw_phy_config_reg_t phy_config; // [9:4]
+    usbdev_reg2hw_wake_config_reg_t wake_config; // [3:0]
   } usbdev_reg2hw_t;
 
   // HW -> register type
diff --git a/hw/ip/usbdev/rtl/usbdev_reg_top.sv b/hw/ip/usbdev/rtl/usbdev_reg_top.sv
index c40ac3b..77ad6e9 100644
--- a/hw/ip/usbdev/rtl/usbdev_reg_top.sv
+++ b/hw/ip/usbdev/rtl/usbdev_reg_top.sv
@@ -9,6 +9,8 @@
 module usbdev_reg_top (
   input clk_i,
   input rst_ni,
+  input clk_usb_48mhz_i,
+  input rst_usb_48mhz_ni,
   input clk_aon_i,
   input rst_aon_ni,
   input  tlul_pkg::tl_h2d_t tl_i,
@@ -157,6 +159,18 @@
   );
 
   // cdc oversampling signals
+    logic sync_usb_48mhz_update;
+  prim_sync_reqack u_usb_48mhz_tgl (
+    .clk_src_i(clk_usb_48mhz_i),
+    .rst_src_ni(rst_usb_48mhz_ni),
+    .clk_dst_i(clk_i),
+    .rst_dst_ni(rst_ni),
+    .req_chk_i(1'b1),
+    .src_req_i(1'b1),
+    .src_ack_o(),
+    .dst_req_o(sync_usb_48mhz_update),
+    .dst_ack_i(sync_usb_48mhz_update)
+  );
     logic sync_aon_update;
   prim_sync_reqack u_aon_tgl (
     .clk_src_i(clk_aon_i),
@@ -267,10 +281,8 @@
   logic alert_test_we;
   logic alert_test_wd;
   logic usbctrl_we;
-  logic usbctrl_enable_qs;
-  logic usbctrl_enable_wd;
-  logic [6:0] usbctrl_device_address_qs;
-  logic [6:0] usbctrl_device_address_wd;
+  logic [22:0] usbctrl_qs;
+  logic usbctrl_busy;
   logic ep_out_enable_we;
   logic ep_out_enable_enable_0_qs;
   logic ep_out_enable_enable_0_wd;
@@ -654,15 +666,85 @@
   logic phy_config_tx_osc_test_mode_qs;
   logic phy_config_tx_osc_test_mode_wd;
   logic wake_config_we;
-  logic wake_config_wake_en_qs;
-  logic wake_config_wake_en_wd;
-  logic wake_config_wake_ack_qs;
-  logic wake_config_wake_ack_wd;
+  logic [1:0] wake_config_qs;
+  logic wake_config_busy;
   logic [9:0] wake_events_qs;
   logic wake_events_busy;
   // Define register CDC handling.
   // CDC handling is done on a per-reg instead of per-field boundary.
 
+  logic  usb_48mhz_usbctrl_enable_qs_int;
+  logic [6:0]  usb_48mhz_usbctrl_device_address_qs_int;
+  logic [22:0] usb_48mhz_usbctrl_d;
+  logic [22:0] usb_48mhz_usbctrl_wdata;
+  logic usb_48mhz_usbctrl_we;
+  logic unused_usb_48mhz_usbctrl_wdata;
+
+  always_comb begin
+    usb_48mhz_usbctrl_d = '0;
+    usb_48mhz_usbctrl_d[0] = usb_48mhz_usbctrl_enable_qs_int;
+    usb_48mhz_usbctrl_d[22:16] = usb_48mhz_usbctrl_device_address_qs_int;
+  end
+
+  prim_reg_cdc #(
+    .DataWidth(23),
+    .ResetVal(23'h0),
+    .BitMask(23'h7f0003)
+  ) u_usbctrl_cdc (
+    .clk_src_i    (clk_i),
+    .rst_src_ni   (rst_ni),
+    .clk_dst_i    (clk_usb_48mhz_i),
+    .rst_dst_ni   (rst_usb_48mhz_ni),
+    .src_update_i (sync_usb_48mhz_update),
+    .src_regwen_i ('0),
+    .src_we_i     (usbctrl_we),
+    .src_re_i     ('0),
+    .src_wd_i     (reg_wdata[22:0]),
+    .src_busy_o   (usbctrl_busy),
+    .src_qs_o     (usbctrl_qs), // for software read back
+    .dst_d_i      (usb_48mhz_usbctrl_d),
+    .dst_we_o     (usb_48mhz_usbctrl_we),
+    .dst_re_o     (),
+    .dst_regwen_o (),
+    .dst_wd_o     (usb_48mhz_usbctrl_wdata)
+  );
+  assign unused_usb_48mhz_usbctrl_wdata = ^usb_48mhz_usbctrl_wdata;
+
+  logic  aon_wake_config_wake_en_qs_int;
+  logic [1:0] aon_wake_config_d;
+  logic [1:0] aon_wake_config_wdata;
+  logic aon_wake_config_we;
+  logic unused_aon_wake_config_wdata;
+
+  always_comb begin
+    aon_wake_config_d = '0;
+    aon_wake_config_d[0] = aon_wake_config_wake_en_qs_int;
+  end
+
+  prim_reg_cdc #(
+    .DataWidth(2),
+    .ResetVal(2'h0),
+    .BitMask(2'h3)
+  ) u_wake_config_cdc (
+    .clk_src_i    (clk_i),
+    .rst_src_ni   (rst_ni),
+    .clk_dst_i    (clk_aon_i),
+    .rst_dst_ni   (rst_aon_ni),
+    .src_update_i (sync_aon_update),
+    .src_regwen_i ('0),
+    .src_we_i     (wake_config_we),
+    .src_re_i     ('0),
+    .src_wd_i     (reg_wdata[1:0]),
+    .src_busy_o   (wake_config_busy),
+    .src_qs_o     (wake_config_qs), // for software read back
+    .dst_d_i      (aon_wake_config_d),
+    .dst_we_o     (aon_wake_config_we),
+    .dst_re_o     (),
+    .dst_regwen_o (),
+    .dst_wd_o     (aon_wake_config_wdata)
+  );
+  assign unused_aon_wake_config_wdata = ^aon_wake_config_wdata;
+
   logic [2:0]  aon_wake_events_state_qs_int;
   logic  aon_wake_events_disconnected_qs_int;
   logic  aon_wake_events_bus_reset_qs_int;
@@ -1815,23 +1897,48 @@
     .SwAccess(prim_subreg_pkg::SwAccessRW),
     .RESVAL  (1'h0)
   ) u_usbctrl_enable (
-    .clk_i   (clk_i),
-    .rst_ni  (rst_ni),
+    .clk_i   (clk_usb_48mhz_i),
+    .rst_ni  (rst_usb_48mhz_ni),
 
     // from register interface
-    .we     (usbctrl_we),
-    .wd     (usbctrl_enable_wd),
+    .we     (usb_48mhz_usbctrl_we),
+    .wd     (usb_48mhz_usbctrl_wdata[0]),
 
     // from internal hardware
     .de     (1'b0),
     .d      ('0),
 
     // to internal hardware
-    .qe     (),
+    .qe     (reg2hw.usbctrl.enable.qe),
     .q      (reg2hw.usbctrl.enable.q),
 
     // to register interface (read)
-    .qs     (usbctrl_enable_qs)
+    .qs     (usb_48mhz_usbctrl_enable_qs_int)
+  );
+
+  //   F[resume_link_active]: 1:1
+  prim_subreg #(
+    .DW      (1),
+    .SwAccess(prim_subreg_pkg::SwAccessWO),
+    .RESVAL  (1'h0)
+  ) u_usbctrl_resume_link_active (
+    .clk_i   (clk_usb_48mhz_i),
+    .rst_ni  (rst_usb_48mhz_ni),
+
+    // from register interface
+    .we     (usb_48mhz_usbctrl_we),
+    .wd     (usb_48mhz_usbctrl_wdata[1]),
+
+    // from internal hardware
+    .de     (1'b0),
+    .d      ('0),
+
+    // to internal hardware
+    .qe     (reg2hw.usbctrl.resume_link_active.qe),
+    .q      (reg2hw.usbctrl.resume_link_active.q),
+
+    // to register interface (read)
+    .qs     ()
   );
 
   //   F[device_address]: 22:16
@@ -1840,23 +1947,23 @@
     .SwAccess(prim_subreg_pkg::SwAccessRW),
     .RESVAL  (7'h0)
   ) u_usbctrl_device_address (
-    .clk_i   (clk_i),
-    .rst_ni  (rst_ni),
+    .clk_i   (clk_usb_48mhz_i),
+    .rst_ni  (rst_usb_48mhz_ni),
 
     // from register interface
-    .we     (usbctrl_we),
-    .wd     (usbctrl_device_address_wd),
+    .we     (usb_48mhz_usbctrl_we),
+    .wd     (usb_48mhz_usbctrl_wdata[22:16]),
 
     // from internal hardware
     .de     (hw2reg.usbctrl.device_address.de),
     .d      (hw2reg.usbctrl.device_address.d),
 
     // to internal hardware
-    .qe     (),
+    .qe     (reg2hw.usbctrl.device_address.qe),
     .q      (reg2hw.usbctrl.device_address.q),
 
     // to register interface (read)
-    .qs     (usbctrl_device_address_qs)
+    .qs     (usb_48mhz_usbctrl_device_address_qs_int)
   );
 
 
@@ -6574,48 +6681,48 @@
     .SwAccess(prim_subreg_pkg::SwAccessRW),
     .RESVAL  (1'h0)
   ) u_wake_config_wake_en (
-    .clk_i   (clk_i),
-    .rst_ni  (rst_ni),
+    .clk_i   (clk_aon_i),
+    .rst_ni  (rst_aon_ni),
 
     // from register interface
-    .we     (wake_config_we),
-    .wd     (wake_config_wake_en_wd),
+    .we     (aon_wake_config_we),
+    .wd     (aon_wake_config_wdata[0]),
 
     // from internal hardware
     .de     (1'b0),
     .d      ('0),
 
     // to internal hardware
-    .qe     (),
+    .qe     (reg2hw.wake_config.wake_en.qe),
     .q      (reg2hw.wake_config.wake_en.q),
 
     // to register interface (read)
-    .qs     (wake_config_wake_en_qs)
+    .qs     (aon_wake_config_wake_en_qs_int)
   );
 
   //   F[wake_ack]: 1:1
   prim_subreg #(
     .DW      (1),
-    .SwAccess(prim_subreg_pkg::SwAccessRW),
+    .SwAccess(prim_subreg_pkg::SwAccessWO),
     .RESVAL  (1'h0)
   ) u_wake_config_wake_ack (
-    .clk_i   (clk_i),
-    .rst_ni  (rst_ni),
+    .clk_i   (clk_aon_i),
+    .rst_ni  (rst_aon_ni),
 
     // from register interface
-    .we     (wake_config_we),
-    .wd     (wake_config_wake_ack_wd),
+    .we     (aon_wake_config_we),
+    .wd     (aon_wake_config_wdata[1]),
 
     // from internal hardware
     .de     (1'b0),
     .d      ('0),
 
     // to internal hardware
-    .qe     (),
+    .qe     (reg2hw.wake_config.wake_ack.qe),
     .q      (reg2hw.wake_config.wake_ack.q),
 
     // to register interface (read)
-    .qs     (wake_config_wake_ack_qs)
+    .qs     ()
   );
 
 
@@ -6886,9 +6993,8 @@
   assign alert_test_wd = reg_wdata[0];
   assign usbctrl_we = addr_hit[4] & reg_we & !reg_error;
 
-  assign usbctrl_enable_wd = reg_wdata[0];
 
-  assign usbctrl_device_address_wd = reg_wdata[22:16];
+
   assign ep_out_enable_we = addr_hit[5] & reg_we & !reg_error;
 
   assign ep_out_enable_enable_0_wd = reg_wdata[0];
@@ -7264,9 +7370,7 @@
   assign phy_config_tx_osc_test_mode_wd = reg_wdata[7];
   assign wake_config_we = addr_hit[32] & reg_we & !reg_error;
 
-  assign wake_config_wake_en_wd = reg_wdata[0];
 
-  assign wake_config_wake_ack_wd = reg_wdata[1];
 
   // Read data return
   always_comb begin
@@ -7337,10 +7441,8 @@
       end
 
       addr_hit[4]: begin
-        reg_rdata_next[0] = usbctrl_enable_qs;
-        reg_rdata_next[22:16] = usbctrl_device_address_qs;
+        reg_rdata_next = DW'(usbctrl_qs);
       end
-
       addr_hit[5]: begin
         reg_rdata_next[0] = ep_out_enable_enable_0_qs;
         reg_rdata_next[1] = ep_out_enable_enable_1_qs;
@@ -7618,10 +7720,8 @@
       end
 
       addr_hit[32]: begin
-        reg_rdata_next[0] = wake_config_wake_en_qs;
-        reg_rdata_next[1] = wake_config_wake_ack_qs;
+        reg_rdata_next = DW'(wake_config_qs);
       end
-
       addr_hit[33]: begin
         reg_rdata_next = DW'(wake_events_qs);
       end
@@ -7641,6 +7741,12 @@
   always_comb begin
     reg_busy_sel = '0;
     unique case (1'b1)
+      addr_hit[4]: begin
+        reg_busy_sel = usbctrl_busy;
+      end
+      addr_hit[32]: begin
+        reg_busy_sel = wake_config_busy;
+      end
       addr_hit[33]: begin
         reg_busy_sel = wake_events_busy;
       end
diff --git a/hw/ip/usbdev/rtl/usbdev_usbif.sv b/hw/ip/usbdev/rtl/usbdev_usbif.sv
index 88235e8..3ff52ae 100644
--- a/hw/ip/usbdev/rtl/usbdev_usbif.sv
+++ b/hw/ip/usbdev/rtl/usbdev_usbif.sv
@@ -78,6 +78,7 @@
   input  logic                     cfg_rx_differential_i, // 1: use differential rx data on usb_d_i
   input  logic                     tx_osc_test_mode_i, // Oscillator test mode: constant JK output
   input  logic [NEndpoints-1:0]    data_toggle_clear_i, // Clear the data toggles for an EP
+  input  logic                     resume_link_active_i, // Jump from LinkPowered to LinkResuming
 
   // status
   output logic                     frame_start_o,
@@ -262,6 +263,7 @@
 
   logic [10:0]     frame_index_raw;
   logic            rx_jjj_det;
+  logic            rx_j_det;
 
   usb_fs_nb_pe #(
     .NumOutEps      (NEndpoints),
@@ -271,6 +273,7 @@
     .clk_48mhz_i           (clk_48mhz_i),
     .rst_ni                (rst_ni),
     .link_reset_i          (link_reset),
+    .link_active_i         (link_active_o),
 
     .cfg_eop_single_bit_i  (cfg_eop_single_bit_i),
     .cfg_rx_differential_i (cfg_rx_differential_i),
@@ -317,6 +320,7 @@
 
     // rx status
     .rx_jjj_det_o          (rx_jjj_det),
+    .rx_j_det_o            (rx_j_det),
 
     // error signals
     .rx_crc_err_o          (rx_crc_err_o),
@@ -357,23 +361,26 @@
   end
 
   usbdev_linkstate u_usbdev_linkstate (
-    .clk_48mhz_i       (clk_48mhz_i),
-    .rst_ni            (rst_ni),
-    .us_tick_i         (us_tick),
-    .usb_sense_i       (usb_sense_i),
-    .usb_dp_i          (usb_dp_i),
-    .usb_dn_i          (usb_dn_i),
-    .usb_oe_i          (usb_oe_o),
-    .rx_jjj_det_i      (rx_jjj_det),
-    .sof_valid_i       (sof_valid),
-    .link_disconnect_o (link_disconnect_o),
-    .link_connect_o    (link_connect_o),
-    .link_reset_o      (link_reset),
-    .link_active_o     (link_active_o),
-    .link_suspend_o    (link_suspend_o),
-    .link_resume_o     (link_resume_o),
-    .link_state_o      (link_state_o),
-    .host_lost_o       (host_lost_o)
+    .clk_48mhz_i           (clk_48mhz_i),
+    .rst_ni                (rst_ni),
+    .us_tick_i             (us_tick),
+    .usb_sense_i           (usb_sense_i),
+    .usb_dp_i              (usb_dp_i),
+    .usb_dn_i              (usb_dn_i),
+    .usb_oe_i              (usb_oe_o),
+    .usb_pullup_en_i       (enable_i),
+    .rx_jjj_det_i          (rx_jjj_det),
+    .rx_j_det_i            (rx_j_det),
+    .sof_valid_i           (sof_valid),
+    .resume_link_active_i  (resume_link_active_i),
+    .link_disconnect_o     (link_disconnect_o),
+    .link_connect_o        (link_connect_o),
+    .link_reset_o          (link_reset),
+    .link_active_o         (link_active_o),
+    .link_suspend_o        (link_suspend_o),
+    .link_resume_o         (link_resume_o),
+    .link_state_o          (link_state_o),
+    .host_lost_o           (host_lost_o)
   );
 
   ////////////////
diff --git a/sw/device/lib/dif/dif_usbdev.c b/sw/device/lib/dif/dif_usbdev.c
index d4e2253..2aa08aa 100644
--- a/sw/device/lib/dif/dif_usbdev.c
+++ b/sw/device/lib/dif/dif_usbdev.c
@@ -725,20 +725,26 @@
       USBDEV_USBSTAT_LINK_STATE_MASK, USBDEV_USBSTAT_LINK_STATE_OFFSET);
 
   switch (val) {
-    case USBDEV_USBSTAT_LINK_STATE_VALUE_DISCONNECT:
+    case USBDEV_USBSTAT_LINK_STATE_VALUE_DISCONNECTED:
       *link_state = kDifUsbdevLinkStateDisconnected;
       break;
     case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED:
       *link_state = kDifUsbdevLinkStatePowered;
       break;
-    case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED_SUSPEND:
-      *link_state = kDifUsbdevLinkStatePoweredSuspend;
+    case USBDEV_USBSTAT_LINK_STATE_VALUE_POWERED_SUSPENDED:
+      *link_state = kDifUsbdevLinkStatePoweredSuspended;
       break;
     case USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE:
       *link_state = kDifUsbdevLinkStateActive;
       break;
-    case USBDEV_USBSTAT_LINK_STATE_VALUE_SUSPEND:
-      *link_state = kDifUsbdevLinkStateSuspend;
+    case USBDEV_USBSTAT_LINK_STATE_VALUE_SUSPENDED:
+      *link_state = kDifUsbdevLinkStateSuspended;
+      break;
+    case USBDEV_USBSTAT_LINK_STATE_VALUE_ACTIVE_NOSOF:
+      *link_state = kDifUsbdevLinkStateActiveNoSof;
+      break;
+    case USBDEV_USBSTAT_LINK_STATE_VALUE_RESUMING:
+      *link_state = kDifUsbdevLinkStateResuming;
       break;
     default:
       return kDifError;
diff --git a/sw/device/lib/dif/dif_usbdev.h b/sw/device/lib/dif/dif_usbdev.h
index 8322ea2..377effe 100644
--- a/sw/device/lib/dif/dif_usbdev.h
+++ b/sw/device/lib/dif/dif_usbdev.h
@@ -579,9 +579,11 @@
 typedef enum dif_usbdev_link_state {
   kDifUsbdevLinkStateDisconnected,
   kDifUsbdevLinkStatePowered,
-  kDifUsbdevLinkStatePoweredSuspend,
+  kDifUsbdevLinkStatePoweredSuspended,
   kDifUsbdevLinkStateActive,
-  kDifUsbdevLinkStateSuspend,
+  kDifUsbdevLinkStateSuspended,
+  kDifUsbdevLinkStateActiveNoSof,
+  kDifUsbdevLinkStateResuming,
 } dif_usbdev_link_state_t;
 
 /**