Add Zephyr support for the Audio DSP on the MT8196 SOC. This is a very similar device to previous designs. Most of this patch is just DTS. The biggest delta is the more complicated second level interrupt controller, though it is still able to be represented using some vaguely clever DTS config over the older intc_mtk_adsp driver. Also the memory layout is slightly different, requiring a little indirection to set the initial boot stack address and log output buffer. And the timer "irq_ack" register bits moved. Signed-off-by: Andy Ross <andyross@google.com>
185 lines
5.6 KiB
C
185 lines
5.6 KiB
C
/* 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 */
|
|
|
|
#ifndef CONFIG_SOC_MT8196
|
|
#define OSTIMER_IRQ_ACK_ENABLE BIT(4) /* read = status, write = enable */
|
|
#define OSTIMER_IRQ_ACK_CLEAR BIT(5)
|
|
#else
|
|
#define OSTIMER_IRQ_ACK_ENABLE BIT(0)
|
|
#define OSTIMER_IRQ_ACK_CLEAR BIT(5)
|
|
#endif
|
|
|
|
#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);
|