blob: 69dcb52034e56edacc37643c95e575cd867ecaa6 [file] [log] [blame]
// 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