summaryrefslogtreecommitdiff
path: root/hw/bsp/sysview_wch_timestamp.h
blob: c11bfe0ab317f1228edb5fc0769c94d382cfe257 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/*
 * SPDX-FileCopyrightText: Copyright (c) 2019 Ha Thach (tinyusb.org)
 * SPDX-License-Identifier: MIT
 *
 * This file is part of the TinyUSB stack.
 */

// SEGGER_SYSVIEW_X_GetTimestamp(), shared by the three ported WCH bare-metal families
// (ch32v20x, ch32v30x, ch583). Each family.c includes this from inside its own
// `#if (CFG_TUD_SYSVIEW || CFG_TUH_SYSVIEW) && CFG_TUSB_OS == OPT_OS_NONE` block -- the only
// place system_ticks/SysTick (defined a few lines above the include site, inside the matching
// `#if CFG_TUSB_OS == OPT_OS_NONE` block) are available -- after defining SYSVIEW_WCH_CLOCK_HZ()
// to its clock source: ch32v20x/ch32v30x use the cached SystemCoreClock global; ch583 has no
// such cache and must call GetSysClock() (a register read + divide, see
// hw/mcu/wch/ch583/EVT/EXAM/SRC/StdPeriphDriver/CH58x_sys.c) -- the same value SysTick_Config()
// itself was given.
//
// RISC-V has no DWT cycle counter, so SystemView takes its timestamp from a hardware timer
// (contract in src/common/tusb_sysview.c: free-running microseconds). The QingKe SysTick is a
// peripheral, not a core register: its CNT counts HCLK cycles and is reloaded to 0 by hardware
// every millisecond (CMP = clock/1000 - 1, see each family's SysTick_Config). Composing the
// millisecond tick with CNT costs no extra peripheral and leaves the 1 ms tick untouched.
//
// Hardware reloads CNT to 0 the instant it matches CMP -- independently of when SysTick_Handler
// actually runs and increments system_ticks. Callers here run under SEGGER_RTT_LOCK() (interrupts
// masked), so a wrap can sit pending for the whole locked section: CNT reads back near 0 while
// system_ticks is still the old value in BOTH reads below, which the stable-read retry can't see
// (system_ticks genuinely hasn't changed yet). Left uncompensated, that is an up-to-1ms backwards
// step versus an event recorded moments earlier at ms*1000 + ~999us. SysTick->SR bit 0 (CNTIF)
// latches on that same hardware wrap regardless of whether the ISR has serviced it yet, so it is
// read alongside cnt and used to compensate the still-pending millisecond. A monotonic clamp is
// kept as belt-and-braces -- callers are serialized by the RTT lock, so the static is race-free
// on this family.
//
// Include-once-per-family, not general-purpose: SEGGER_SYSVIEW_X_GetTimestamp() below is a
// non-static linker-visible hook (SEGGER calls it by name) -- a second include in the same TU
// is a redefinition error, a second .c in the same family including it is a duplicate symbol at
// link. The #ifndef SYSVIEW_WCH_CLOCK_HZ check below only catches "forgot to configure before
// including", not a repeat include; TUSB_BSP_SYSVIEW_WCH_TIMESTAMP_H_ below is the real guard.
#ifndef TUSB_BSP_SYSVIEW_WCH_TIMESTAMP_H_
#define TUSB_BSP_SYSVIEW_WCH_TIMESTAMP_H_

#ifndef SYSVIEW_WCH_CLOCK_HZ
  #error "define SYSVIEW_WCH_CLOCK_HZ() to the family's clock source before including this file"
#endif

uint32_t SEGGER_SYSVIEW_X_GetTimestamp(void) {
  // W11: SYSVIEW_WCH_CLOCK_HZ() is a peripheral-register read + divide (ch583's GetSysClock())
  // or at least a divide on the cached SystemCoreClock (ch32v20x/v30x) -- redone on every
  // recorded event inside this interrupt-masked (SEGGER_RTT_LOCK()) window. The clock never
  // changes after board_init, so latch it once instead. Benign race on the first call (callers
  // are serialized by the RTT lock anyway, so this is theoretical): two callers computing this
  // concurrently would both compute and store the same value.
  static uint32_t cycles_per_us;
  if (cycles_per_us == 0) {
    cycles_per_us = SYSVIEW_WCH_CLOCK_HZ() / 1000000u;
  }
  uint32_t ms, cnt, pend;
  do { // re-read the tick: CNT wraps to 0 exactly when system_ticks increments
    ms = system_ticks;
    // SR before CNT: a wrap landing between the two reads must not pair a
    // pre-wrap CNT with a post-wrap pending flag (that adds a phantom ~1 ms)
    pend = SysTick->SR & 1u;
    cnt = (uint32_t) SysTick->CNT;
  } while (ms != system_ticks || pend != (SysTick->SR & 1u));
  uint32_t t = (ms + (pend ? 1u : 0u)) * 1000u + (cycles_per_us ? cnt / cycles_per_us : 0);
  static uint32_t last;
  if ((int32_t) (t - last) < 0) { t = last; }
  last = t;
  return t;
}

#undef SYSVIEW_WCH_CLOCK_HZ

#endif /* TUSB_BSP_SYSVIEW_WCH_TIMESTAMP_H_ */