| // Copyright lowRISC contributors. |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| // SPDX-License-Identifier: Apache-2.0 |
| // |
| // Link state detection |
| // |
| |
| module usbdev_linkstate ( |
| input clk_48mhz_i, |
| input rst_ni, |
| input us_tick_i, |
| input usb_sense_i, |
| input usb_rx_dp_i, |
| input usb_rx_dn_i, |
| input sof_valid_i, |
| output logic link_disconnect_o, |
| output logic link_reset_o, |
| output logic link_suspend_o, |
| output logic link_resume_o, |
| output logic [1:0] link_state_o, |
| output logic host_lost_o |
| ); |
| |
| localparam SUSPEND_TIMEOUT = 12'd3000; // 3ms by spec |
| localparam RESET_TIMEOUT = 12'd3; // 3us. Can be 2.5us - 10ms by spec |
| |
| typedef enum logic [2:0] { |
| LinkDisconnect = 3'h000, |
| // Reset state |
| LinkReset = 3'b001, |
| // Suspend state |
| LinkSuspend = 3'b010, |
| // Active states |
| LinkActive = 3'b100, |
| LinkWaitSuspend = 3'b101, |
| LinkWaitReset = 3'b110 |
| } link_state_e; |
| |
| link_state_e link, link_next; |
| logic link_active, resume_next; |
| logic rx_dp, rx_dn; |
| logic line_se0, line_idle; |
| logic see_se0, see_idle; |
| logic [11:0] timeout, timeout_next; |
| logic time_expire, waiting, waiting_next; |
| |
| assign link_disconnect_o = (link == LinkDisconnect); |
| assign link_reset_o = (link == LinkReset); |
| assign link_suspend_o = (link == LinkSuspend); |
| assign link_active = (link == LinkActive) | |
| (link == LinkWaitSuspend) | |
| (link == LinkWaitReset); |
| // re-encode to enum values from register description |
| // (so sw doesn't have to deal with changes between the active states) |
| assign link_state_o = link_disconnect_o ? 2'h0 : |
| link_suspend_o ? 2'h2 : |
| link_reset_o ? 2'h1 : 2'h3; |
| |
| prim_flop_2sync #(.Width(2)) syncrx ( |
| .clk_i (clk_48mhz_i), |
| .rst_ni (rst_ni), |
| .d ({usb_rx_dp_i, usb_rx_dn_i}), |
| .q ({rx_dp, rx_dn}) |
| ); |
| assign line_se0 = (rx_dp == 1'b0) & (rx_dn == 1'b0); |
| assign line_idle = (rx_dp == 1'b1) & (rx_dn == 1'b0); // same as J |
| |
| // four ticks is a bit time |
| // Could completely filter out 2-cycle EOP SE0 here but |
| // does not seem needed |
| prim_filter #(.Cycles(6)) filter_se0 ( |
| .clk_i (clk_48mhz_i), |
| .rst_ni (rst_ni), |
| .enable_i (1'b1), |
| .filter_i (line_se0), |
| .filter_o (see_se0) |
| ); |
| |
| prim_filter #(.Cycles(6)) filter_idle ( |
| .clk_i (clk_48mhz_i), |
| .rst_ni (rst_ni), |
| .enable_i (1'b1), |
| .filter_i (line_idle), |
| .filter_o (see_idle) |
| ); |
| |
| always_comb begin |
| link_next = link; |
| |
| // If VBUS ever goes away the link has disconnected |
| if (!usb_sense_i) begin |
| link_next = LinkDisconnect; |
| end else begin |
| case (link) |
| LinkDisconnect: begin |
| if (usb_sense_i) begin |
| link_next = LinkReset; |
| end |
| end |
| |
| LinkWaitReset: begin |
| if (!see_se0) begin |
| link_next = LinkActive; |
| end else if (time_expire) begin |
| link_next = LinkReset; |
| end |
| end |
| |
| LinkReset: begin |
| if (!see_se0) begin |
| link_next = LinkActive; |
| end |
| end |
| |
| LinkWaitSuspend: begin |
| if (!see_idle) begin |
| link_next = LinkActive; |
| end else if (time_expire) begin |
| link_next = LinkSuspend; |
| end |
| end |
| |
| LinkSuspend: begin |
| if (!see_idle) begin |
| link_next = LinkActive; |
| end |
| end |
| |
| LinkActive: begin |
| if (see_se0) begin |
| link_next = LinkWaitReset; |
| end else if (see_idle) begin |
| link_next = LinkWaitSuspend; |
| end |
| end |
| |
| default: begin |
| link_next = LinkDisconnect; |
| end |
| endcase // case (link) |
| end |
| end |
| assign waiting_next = (link_next == LinkWaitReset) | |
| (link_next == LinkWaitSuspend); |
| assign timeout_next = (link_next == LinkWaitReset) ? RESET_TIMEOUT : SUSPEND_TIMEOUT; |
| assign resume_next = (link == LinkSuspend) & (link_next == LinkActive); |
| |
| always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| link <= LinkDisconnect; |
| timeout <= '0; |
| waiting <= 1'b0; |
| link_resume_o <= 1'b0; |
| end else begin |
| link <= link_next; |
| timeout <= timeout_next; |
| waiting <= waiting_next; |
| link_resume_o <= resume_next; |
| end |
| end |
| |
| logic [11:0] activity_timer; // Max timeout 3ms == 3000us |
| |
| always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| activity_timer <= '0; |
| time_expire <= 1'b0; |
| end else begin |
| if (!waiting) begin |
| activity_timer <= '0; |
| time_expire <= 1'b0; |
| end else if (activity_timer > timeout) begin |
| time_expire <= 1'b1; |
| end else if (us_tick_i) begin |
| activity_timer <= activity_timer + 1'b1; |
| end |
| end |
| end |
| |
| // host_lost if no sof in 4.096ms (supposed to be every 1ms) |
| // and the link is active |
| logic [12:0] host_presence_timer; |
| |
| assign host_lost_o = host_presence_timer[12]; |
| always_ff @(posedge clk_48mhz_i or negedge rst_ni) begin |
| if (!rst_ni) begin |
| host_presence_timer <= '0; |
| end else begin |
| if (sof_valid_i || !link_active) begin |
| host_presence_timer <= '0; |
| end else if (us_tick_i && !host_lost_o) begin |
| host_presence_timer <= host_presence_timer + 1'b1; |
| end |
| end |
| end |
| |
| endmodule |