forked from CTCaer/hekate
b674624ad0
usage: `isleep(ILOOP(instructions))` Each loop is 3 cycles, or approximately 7.35ns on 408MHz CPU clock.
121 lines
3.1 KiB
C
121 lines
3.1 KiB
C
/*
|
|
* Timer/Watchdog driver for Tegra X1
|
|
*
|
|
* Copyright (c) 2019 CTCaer
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <soc/bpmp.h>
|
|
#include <soc/irq.h>
|
|
#include <soc/timer.h>
|
|
#include <soc/t210.h>
|
|
#include <utils/types.h>
|
|
|
|
#define EXCP_TYPE_ADDR 0x4003FFF8
|
|
#define EXCP_TYPE_WDT 0x544457 // "WDT".
|
|
|
|
u32 get_tmr_s()
|
|
{
|
|
(void)RTC(APBDEV_RTC_MILLI_SECONDS);
|
|
return (u32)RTC(APBDEV_RTC_SECONDS);
|
|
}
|
|
|
|
u32 get_tmr_ms()
|
|
{
|
|
// The registers must be read with the following order:
|
|
// RTC_MILLI_SECONDS (0x10) -> RTC_SHADOW_SECONDS (0xC)
|
|
return (u32)(RTC(APBDEV_RTC_MILLI_SECONDS) + (RTC(APBDEV_RTC_SHADOW_SECONDS) * 1000));
|
|
}
|
|
|
|
u32 get_tmr_us()
|
|
{
|
|
return (u32)TMR(TIMERUS_CNTR_1US);
|
|
}
|
|
|
|
void msleep(u32 ms)
|
|
{
|
|
#ifdef USE_RTC_TIMER
|
|
u32 start = (u32)RTC(APBDEV_RTC_MILLI_SECONDS) + (RTC(APBDEV_RTC_SHADOW_SECONDS) * 1000);
|
|
// Casting to u32 is important!
|
|
while (((u32)(RTC(APBDEV_RTC_MILLI_SECONDS) + (RTC(APBDEV_RTC_SHADOW_SECONDS) * 1000)) - start) <= ms)
|
|
;
|
|
#else
|
|
bpmp_msleep(ms);
|
|
#endif
|
|
}
|
|
|
|
void usleep(u32 us)
|
|
{
|
|
#ifdef USE_RTC_TIMER
|
|
u32 start = (u32)TMR(TIMERUS_CNTR_1US);
|
|
|
|
// Check if timer is at upper limits and use BPMP sleep so it doesn't wake up immediately.
|
|
if ((start + us) < start)
|
|
bpmp_usleep(us);
|
|
else
|
|
while ((u32)(TMR(TIMERUS_CNTR_1US) - start) <= us) // Casting to u32 is important!
|
|
;
|
|
#else
|
|
bpmp_usleep(us);
|
|
#endif
|
|
}
|
|
|
|
// Instruction wait loop. Each loop is 3 cycles (SUBS+BGT). Usage: isleep(ILOOP(instr)). Base 408MHz: 7.35ns.
|
|
void __attribute__((target("arm"))) isleep(u32 is)
|
|
{
|
|
asm volatile( "0:" "SUBS %[is_cnt], #1;" "BGT 0b;" : [is_cnt] "+r" (is));
|
|
}
|
|
|
|
void timer_usleep(u32 us)
|
|
{
|
|
TMR(TIMER_TMR8_TMR_PTV) = TIMER_EN | us;
|
|
|
|
irq_wait_event(IRQ_TMR8);
|
|
|
|
TMR(TIMER_TMR8_TMR_PCR) = TIMER_INTR_CLR;
|
|
}
|
|
|
|
void watchdog_start(u32 us, u32 mode)
|
|
{
|
|
// WDT4 is for BPMP.
|
|
TMR(TIMER_WDT4_UNLOCK_PATTERN) = TIMER_MAGIC_PTRN;
|
|
TMR(TIMER_TMR9_TMR_PTV) = TIMER_EN | TIMER_PER_EN | us;
|
|
TMR(TIMER_WDT4_CONFIG) = TIMER_SRC(9) | TIMER_PER(1) | mode;
|
|
TMR(TIMER_WDT4_COMMAND) = TIMER_START_CNT;
|
|
}
|
|
|
|
void watchdog_end()
|
|
{
|
|
// WDT4 is for BPMP.
|
|
TMR(TIMER_TMR9_TMR_PTV) = 0;
|
|
TMR(TIMER_WDT4_UNLOCK_PATTERN) = TIMER_MAGIC_PTRN;
|
|
TMR(TIMER_WDT4_COMMAND) = TIMER_START_CNT; // Re-arm to clear any interrupts.
|
|
TMR(TIMER_WDT4_COMMAND) = TIMER_CNT_DISABLE;
|
|
TMR(TIMER_TMR9_TMR_PCR) = TIMER_INTR_CLR;
|
|
}
|
|
|
|
void watchdog_handle()
|
|
{
|
|
// Disable watchdog and clear its interrupts.
|
|
watchdog_end();
|
|
|
|
// Set watchdog magic.
|
|
*(u32 *)EXCP_TYPE_ADDR = EXCP_TYPE_WDT;
|
|
}
|
|
|
|
bool watchdog_fired()
|
|
{
|
|
// Return if watchdog got fired. User handles clearing.
|
|
return (*(u32 *)EXCP_TYPE_ADDR == EXCP_TYPE_WDT);
|
|
} |