drivers/timer: MediaTek audio DSP timer device
These devices have a somewhat odd hybrid design, with a free-running 64 bit up counter but no comparator. Instead interrupts are triggered by (one of an array of) 32 bit down counters with reset (a-la SysTick, but without the 24 bit precision issues). The combination actually results in a fairly simple driver as we can skip the comparator rounding math. Signed-off-by: Andy Ross <andyross@google.com>
This commit is contained in:
parent
3f07d70cf3
commit
e0c56f1998
4 changed files with 193 additions and 0 deletions
|
@ -35,3 +35,4 @@ zephyr_library_sources_ifdef(CONFIG_STM32_LPTIM_TIMER stm32_lptim_timer.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_XLNX_PSTTC_TIMER xlnx_psttc_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_XTENSA_TIMER xtensa_sys_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_SMARTBOND_TIMER smartbond_timer.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_MTK_ADSP_TIMER mtk_adsp_timer.c)
|
||||
|
|
|
@ -94,6 +94,7 @@ source "drivers/timer/Kconfig.smartbond"
|
|||
source "drivers/timer/Kconfig.stm32_lptim"
|
||||
source "drivers/timer/Kconfig.xlnx_psttc"
|
||||
source "drivers/timer/Kconfig.xtensa"
|
||||
source "drivers/timer/Kconfig.mtk_adsp"
|
||||
|
||||
endmenu
|
||||
|
||||
|
|
11
drivers/timer/Kconfig.mtk_adsp
Normal file
11
drivers/timer/Kconfig.mtk_adsp
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Copyright 2023 The ChromiumOS Authors
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config MTK_ADSP_TIMER
|
||||
bool "MediaTek Audio DSP timer"
|
||||
select TICKLESS_CAPABLE
|
||||
select TIMER_HAS_64BIT_CYCLE_COUNTER
|
||||
select SYSTEM_CLOCK_LOCK_FREE_COUNT
|
||||
help
|
||||
MediaTek MT81xx Audio DSPs have a 13 Mhz wall clock timer
|
||||
for system time that is independent of CPU speed.
|
180
drivers/timer/mtk_adsp_timer.c
Normal file
180
drivers/timer/mtk_adsp_timer.c
Normal file
|
@ -0,0 +1,180 @@
|
|||
/* Copyright 2023 The ChromiumOS Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <zephyr/spinlock.h>
|
||||
#include <zephyr/init.h>
|
||||
#include <zephyr/drivers/timer/system_timer.h>
|
||||
|
||||
#define OSTIMER64_BASE DT_REG_ADDR(DT_NODELABEL(ostimer64))
|
||||
#define OSTIMER_BASE DT_REG_ADDR(DT_NODELABEL(ostimer0))
|
||||
|
||||
/*
|
||||
* This device has a LOT of timer hardware. There are SIX
|
||||
* instantiated devices, with THREE different interfaces! Not
|
||||
* including the three Xtensa CCOUNT timers!
|
||||
*
|
||||
* In practice only "ostimer0" is used as an interrupt source by the
|
||||
* original SOF code, and the "ostimer64" and "platform" timers
|
||||
* reflect the same underlying clock (though they're different
|
||||
* counters with different values). There is also a "ptimer" device,
|
||||
* which is unused by SOF and not exercised by this driver.
|
||||
*
|
||||
* The driver architecture itself is sort of a hybrid of what other
|
||||
* Zephyr drivers use: there is no (or at least no documented)
|
||||
* comparator facility. The "ostimer64" is used as the system clock,
|
||||
* which is a 13 MHz 64 bit up-counter. But timeout interrupts are
|
||||
* delivered by ostimers[0], which is a 32 bit (!) down-counter (!!)
|
||||
* running at twice (!!!) the rate: 26MHz. Testing shows they're
|
||||
* slaved the same underlying clock -- they don't skew relative to
|
||||
* each other.
|
||||
*/
|
||||
struct mtk_ostimer {
|
||||
unsigned int con;
|
||||
unsigned int rst;
|
||||
unsigned int cur;
|
||||
unsigned int irq_ack;
|
||||
};
|
||||
|
||||
struct mtk_ostimer64 {
|
||||
unsigned int con;
|
||||
unsigned int init_l;
|
||||
unsigned int init_h;
|
||||
unsigned int cur_l;
|
||||
unsigned int cur_h;
|
||||
unsigned int tval_h;
|
||||
unsigned int irq_ack;
|
||||
};
|
||||
|
||||
#define OSTIMER64 (*(volatile struct mtk_ostimer64 *)OSTIMER64_BASE)
|
||||
|
||||
#define OSTIMERS ((volatile struct mtk_ostimer *)OSTIMER_BASE)
|
||||
|
||||
#define OSTIMER_CON_ENABLE BIT(0)
|
||||
#define OSTIMER_CON_CLKSRC_MASK 0x30
|
||||
#define OSTIMER_CON_CLKSRC_32K 0x00 /* 32768 Hz */
|
||||
#define OSTIMER_CON_CLKSRC_26M 0x10 /* 26 MHz */
|
||||
#define OSTIMER_CON_CLKSRC_BCLK 0x20 /* CPU speed, 720 MHz */
|
||||
#define OSTIMER_CON_CLKSRC_PCLK 0x30 /* ~312 MHz experimentally */
|
||||
|
||||
#define OSTIMER_IRQ_ACK_ENABLE BIT(4) /* read = status, write = enable */
|
||||
#define OSTIMER_IRQ_ACK_CLEAR BIT(5)
|
||||
|
||||
#define OST64_HZ 13000000U
|
||||
#define OST_HZ 26000000U
|
||||
#define OST64_PER_TICK (OST64_HZ / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
|
||||
#define OST_PER_TICK (OST_HZ / CONFIG_SYS_CLOCK_TICKS_PER_SEC)
|
||||
|
||||
#define MAX_TICKS ((0xffffffffU - OST_PER_TICK) / OST_PER_TICK)
|
||||
#define CYC64_MAX (0xffffffff - OST64_PER_TICK)
|
||||
|
||||
static struct k_spinlock lock;
|
||||
|
||||
static uint64_t last_announce;
|
||||
|
||||
uint32_t sys_clock_cycle_get_32(void)
|
||||
{
|
||||
return OSTIMER64.cur_l;
|
||||
}
|
||||
|
||||
uint64_t sys_clock_cycle_get_64(void)
|
||||
{
|
||||
uint32_t l, h0, h1;
|
||||
|
||||
do {
|
||||
h0 = OSTIMER64.cur_h;
|
||||
l = OSTIMER64.cur_l;
|
||||
h1 = OSTIMER64.cur_h;
|
||||
} while (h0 != h1);
|
||||
|
||||
return (((uint64_t)h0) << 32) | l;
|
||||
}
|
||||
|
||||
void sys_clock_set_timeout(int32_t ticks, bool idle)
|
||||
{
|
||||
/* Compute desired expiration time */
|
||||
uint64_t now = sys_clock_cycle_get_64();
|
||||
uint64_t end = now + CLAMP(ticks - 1, 0, MAX_TICKS) * OST64_PER_TICK;
|
||||
uint32_t dt = (uint32_t)MIN(end - last_announce, CYC64_MAX);
|
||||
|
||||
/* Round up to tick boundary */
|
||||
dt = ((dt + OST64_PER_TICK - 1) / OST64_PER_TICK) * OST64_PER_TICK;
|
||||
|
||||
/* Convert to "fast" OSTIMER[0] cycles! */
|
||||
uint32_t cyc = 2 * (dt - (uint32_t)(now - last_announce));
|
||||
|
||||
/* Writes to RST need to be done when the device is disabled,
|
||||
* and automatically reset CUR (which reads zero while disabled)
|
||||
*/
|
||||
OSTIMERS[0].con &= ~OSTIMER_CON_ENABLE;
|
||||
OSTIMERS[0].rst = cyc;
|
||||
OSTIMERS[0].irq_ack |= OSTIMER_IRQ_ACK_CLEAR;
|
||||
OSTIMERS[0].irq_ack |= OSTIMER_IRQ_ACK_ENABLE;
|
||||
OSTIMERS[0].con |= OSTIMER_CON_ENABLE;
|
||||
}
|
||||
|
||||
uint32_t sys_clock_elapsed(void)
|
||||
{
|
||||
k_spinlock_key_t key = k_spin_lock(&lock);
|
||||
uint32_t ret;
|
||||
|
||||
ret = (uint32_t)((sys_clock_cycle_get_64() - last_announce)
|
||||
/ OST64_PER_TICK);
|
||||
k_spin_unlock(&lock, key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void timer_isr(__maybe_unused void *arg)
|
||||
{
|
||||
/* Note: no locking. As it happens, on MT8195/8186/8188 all
|
||||
* Zephyr-usable interrupts are delivered at the same level.
|
||||
* So we can't be preempted and there's actually no need to
|
||||
* take a spinlock here. But ideally we should verify/detect
|
||||
* this instead of trusting blindly; this is fragile if future
|
||||
* devices add nested interrupts.
|
||||
*/
|
||||
uint64_t dcyc = sys_clock_cycle_get_64() - last_announce;
|
||||
uint64_t ticks = dcyc / OST64_PER_TICK;
|
||||
|
||||
/* Leave the device disabled after clearing the interrupt,
|
||||
* sys_clock_set_timeout() is responsible for turning it back
|
||||
* on.
|
||||
*/
|
||||
OSTIMERS[0].irq_ack |= OSTIMER_IRQ_ACK_CLEAR;
|
||||
OSTIMERS[0].con &= ~OSTIMER_CON_ENABLE;
|
||||
OSTIMERS[0].irq_ack &= ~OSTIMER_IRQ_ACK_ENABLE;
|
||||
|
||||
last_announce += ticks * OST64_PER_TICK;
|
||||
sys_clock_announce(ticks);
|
||||
|
||||
if (!IS_ENABLED(CONFIG_TICKLESS_KERNEL)) {
|
||||
sys_clock_set_timeout(1, false);
|
||||
}
|
||||
}
|
||||
|
||||
static int mtk_adsp_timer_init(void)
|
||||
{
|
||||
IRQ_CONNECT(DT_IRQN(DT_NODELABEL(ostimer0)), 0, timer_isr, 0, 0);
|
||||
irq_enable(DT_IRQN(DT_NODELABEL(ostimer0)));
|
||||
|
||||
/* Disable all timers */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
OSTIMERS[i].con &= ~OSTIMER_CON_ENABLE;
|
||||
OSTIMERS[i].irq_ack |= OSTIMER_IRQ_ACK_CLEAR;
|
||||
OSTIMERS[i].irq_ack &= ~OSTIMER_IRQ_ACK_ENABLE;
|
||||
}
|
||||
|
||||
/* Set them up to use the same clock. Note that OSTIMER64 has
|
||||
* a built-in divide by two (or it's configurable and I don't
|
||||
* know the register) and exposes a 13 MHz counter!
|
||||
*/
|
||||
OSTIMERS[0].con = ((OSTIMERS[0].con & ~OSTIMER_CON_CLKSRC_MASK)
|
||||
| OSTIMER_CON_CLKSRC_26M);
|
||||
OSTIMERS[0].con |= OSTIMER_CON_ENABLE;
|
||||
|
||||
/* Clock is free running and survives reset, doesn't start at zero */
|
||||
last_announce = sys_clock_cycle_get_64();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
SYS_INIT(mtk_adsp_timer_init, PRE_KERNEL_2, CONFIG_SYSTEM_CLOCK_INIT_PRIORITY);
|
Loading…
Add table
Add a link
Reference in a new issue