blob: 841875a0105957bcc939768edc768f8ca58a7bf2 [file] [log] [blame]
// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
// Always On USB wake detect
//
module usbdev_aon_wake import usbdev_pkg::*;(
input logic clk_aon_i,
input logic rst_aon_ni,
// the system to which usb belongs has entered low power
input logic low_power_alw_i,
// These come from the chip pin
input logic usb_dp_async_alw_i,
input logic usb_dn_async_alw_i,
// These come from post pinmux sleep handling logic
input logic usb_dppullup_en_alw_i,
input logic usb_dnpullup_en_alw_i,
// Register signals from IP
input logic usb_out_of_rst_upwr_i,
input logic usb_aon_wake_en_upwr_i,
input logic usb_aon_woken_upwr_i,
// Status from IP, must be valid for long enough for aon clock to catch (>15us)
input logic usb_suspended_upwr_i,
// wake/powerup request
output logic wake_req_alw_o,
// state debug information
output awk_state_e state_debug_o
);
awk_state_e astate_d, astate_q;
logic suspend_req_async, suspend_req;
logic wake_ack_async, wake_ack;
logic low_power_async, low_power;
// note the _upwr signals are only valid when usb_out_of_rst_upwr_i is set
assign suspend_req_async = usb_aon_wake_en_upwr_i & usb_suspended_upwr_i & usb_out_of_rst_upwr_i;
assign wake_ack_async = usb_aon_woken_upwr_i & usb_out_of_rst_upwr_i;
assign low_power_async = low_power_alw_i & ~usb_out_of_rst_upwr_i;
// The suspend_req / wake ack / low power construction come from multiple clock domains.
// As a result the 2 flop sync could glitch for up to 1 cycle. Place a filter after
// the two flop sync to passthrough the value only when stable.
logic [2:0] filter_cdc_in, filter_cdc_out;
assign filter_cdc_in = {low_power_async, suspend_req_async, wake_ack_async};
for (genvar i = 0; i < 3; i++) begin : gen_filters
prim_filter #(
.AsyncOn(1), // Instantiate 2-stage synchronizer
.Cycles(2)
) u_filter (
.clk_i(clk_aon_i),
.rst_ni(rst_aon_ni),
.enable_i(1'b1),
.filter_i(filter_cdc_in[i]),
.filter_o(filter_cdc_out[i])
);
end
assign {low_power, suspend_req, wake_ack} = filter_cdc_out;
logic notidle_async;
logic wake_req;
// In suspend it is the device pullup that sets the line state
// so if the input value differs then the host is doing something
// This covers both host generated wake (J->K) and host generated reset (J->SE0)
// Use of the pullups takes care of pinflipping
assign notidle_async = (usb_dp_async_alw_i != usb_dppullup_en_alw_i) |
(usb_dn_async_alw_i != usb_dnpullup_en_alw_i);
// aon clock is ~200kHz so 4 cycle filter is about 20us
// as well as noise debounce this gives the main IP time to detect resume if it didn't turn off
prim_filter #(
.AsyncOn(1), // Instantiate 2-stage synchronizer
.Cycles(4)
) filter_activity (
.clk_i (clk_aon_i),
.rst_ni (rst_aon_ni),
.enable_i (1'b1),
.filter_i (notidle_async),
.filter_o (wake_req)
);
always_comb begin : proc_awk_fsm
astate_d = astate_q;
unique case (astate_q)
// No aon suspend entry has been requested or detected
AwkIdle: begin
if (suspend_req) begin
astate_d = AwkTrigUon;
end
end
// Suspend has been requested but the USB IP is still alive.
// If the system progresses into low power, wait for wakeup request.
// If before the system progresses into low power the suspend request is lost,
// go manage the interruption
AwkTrigUon: begin
// We are trying to juggle when the usb is no longer detecting resumes.
// This could be when the usb is in reset, or when the system cuts off
// clocking to usb.
// If low power and the suspend request drop at the same time (race condition),
// prioritize low power since the system is already committed to enter low power.
if (low_power) begin
astate_d = AwkTrigUoff;
end else if (!suspend_req) begin
astate_d = AwkWokenUon;
end
end
// The link went not-idle before the USB IP powered off
// It could be about to power down, it could manage the wake, or this was a glitch
// If wake_ack is seen, that means software already handled the resume.
// If suspend_req is seen again, it means th1e !suspend_req seen was just a glitch
// If low power conditions are seen, this means even though there was a glitch / resume,
// the system went to low power anyways, we should now follow the normal resume routine.
AwkWokenUon: begin
if (wake_ack) begin
astate_d = AwkIdle;
end else if (suspend_req) begin
astate_d = AwkTrigUon;
end else if (low_power) begin
astate_d = AwkTrigUoff;
end
end
// Suspend has been enetered and the USB IP is in low power
AwkTrigUoff: begin
if (wake_req) begin
astate_d = AwkWoken;
end
end
// The USB IP was in low power and the link went not-idle, time to wake up
AwkWoken: begin
if (wake_ack) begin
astate_d = AwkIdle;
end
end
default : astate_d = AwkIdle;
endcase
end
`ASSERT(StateValid_A, astate_q inside {AwkIdle, AwkTrigUon, AwkWokenUon, AwkTrigUoff, AwkWoken},
clk_aon_i, !rst_aon_ni)
always_ff @(posedge clk_aon_i or negedge rst_aon_ni) begin : proc_reg_awk
if (!rst_aon_ni) begin
astate_q <= AwkIdle;
end else begin
astate_q <= astate_d;
end
end
assign wake_req_alw_o = (astate_q == AwkWoken);
assign state_debug_o = astate_q;
// The wakeup signal is not latched in the pwrmgr so must be held until acked by software
`ASSERT(UsbWkupStable_A, wake_req_alw_o |=> wake_req_alw_o ||
$past(wake_ack) && !low_power_alw_i, clk_aon_i, !rst_aon_ni)
endmodule