From fb6d9301ff8cbd2dd42a4e05da1efa95c65945bc Mon Sep 17 00:00:00 2001 From: Indan Zupancic Date: Wed, 4 Dec 2024 18:24:14 +0000 Subject: [PATCH] i.MX 93: Add Low Power Timer (LPTMR) Driver Only used if the Arm generic timer cannot be used. The clock source is configurable. The default is the 32.768 kHz clock, as that is more accurate for timekeeping and keeps running when the board is in low power state and can wake up the CPU. Retrieving the time takes about 0.5 us, so this timer is very slow. Both LPMTR1 and LPTMR2 are used because you are not allowed to change the compare value while the timer is running. Signed-off-by: Indan Zupancic --- libplatsupport/CMakeLists.txt | 12 ++ libplatsupport/src/plat/imx93/lptmr.c | 249 ++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 libplatsupport/src/plat/imx93/lptmr.c diff --git a/libplatsupport/CMakeLists.txt b/libplatsupport/CMakeLists.txt index 3e393cb2..23993c8d 100644 --- a/libplatsupport/CMakeLists.txt +++ b/libplatsupport/CMakeLists.txt @@ -35,6 +35,18 @@ config_choice( "ega;LibPlatSupportX86ConsoleDeviceEGA;LIB_PLAT_SUPPORT_SERIAL_TEXT_EGA;KernelPlatPC99" ) +config_choice( + LibPlatSupportLPTMRclock + LIB_PLAT_SUPPORT_LPTMR_CLOCK + "Which input clock to use for the Low Power Timer\ + 32.768 kHz -> High accuracy, but low precision.\ + 24 MHz -> Higher precision, but lower accuracy. Synchronous with the scheduling clock." + "32kHz;LibPlatSupportLPTMRclock24MHz;LIB_PLAT_SUPPORT_LPTMR_CLOCK_32kHZ;KernelPlatformIMX93" + "24MHz;LibPlatSupportLPTMRclock32kHz;LIB_PLAT_SUPPORT_LPTMR_CLOCK_24MHz;KernelPlatformIMX93" +) + +mark_as_advanced(CLEAR LibPlatSupportX86ConsoleDevice LibPlatSupportLPTMRclock) + set(LibPlatSupportMach "") if(KernelPlatformRpi3 OR KernelPlatformRpi4) set(LibPlatSupportMach "bcm") diff --git a/libplatsupport/src/plat/imx93/lptmr.c b/libplatsupport/src/plat/imx93/lptmr.c new file mode 100644 index 00000000..20124dc7 --- /dev/null +++ b/libplatsupport/src/plat/imx93/lptmr.c @@ -0,0 +1,249 @@ +/* + * Copyright 2024, Indan Zupancic + * + * SPDX-License-Identifier: BSD-2-Clause + * + * Low Power Timer driver. + * The i.MX93 has two of them, LPTMR1 and LPTMR2. + */ +#include +#include +#include +#include +#include + +/* Use Arm Generic Timer if available: */ +#if !(defined(CONFIG_EXPORT_PCNT_USER) && defined(CONFIG_EXPORT_PTMR_USER)) + +#define LPTMR1_ADDR 0x44300000 +#define LPTMR2_ADDR 0x424D0000 + +#define LPTMR1_IRQ (32 + 18) +#define LPTMR2_IRQ (32 + 67) + +#define LPTMR_CSR_TCF (1u << 7) // Timer Compare Flag (W1C) +#define LPTMR_CSR_TIE (1u << 6) // Timer Interrupt Enable +#define LPTMR_CSR_TFC (1u << 2) // Timer Free-running Counter +#define LPTMR_CSR_TMS (1u << 1) // Timer Mode Selection +#define LPTMR_CSR_TEN (1u << 0) // Timer Enable + +#define LPTMR_PSR_PRESCALE(n) ((((n) - 1) & 0xf) << 3) // Prescale value in bits +#define LPTMR_PSR_PBYP (1u << 2) // Prescaler and Glitch Filter Bypass +/* Prescaler and Glitch Filter Clock Select */ +#define LPTMR_PSR_PCS_32kHz 2 // ipg_clk_32kHz: Fixed 32.768 kHz +#define LPTMR_PSR_PCS_24MHz 0 // ipg_clk_irclk: From CCM + +#ifdef CONFIG_LIB_PLAT_SUPPORT_LPTMR_CLOCK_32kHZ +#define LPTMR_PSR (LPTMR_PSR_PCS_32kHz | LPTMR_PSR_PBYP) +#define FREQ 32768 +#elif defined(CONFIG_LIB_PLAT_SUPPORT_LPTMR_CLOCK_24MHz) +#define LPTMR_PSR (LPTMR_PSR_PCS_24MHz | LPTMR_PSR_PRESCALE(7)) +#define FREQ (24000000 / 128) +#else +#error "LPTMR: Unknown clock source" +#endif + +#define NS_PER_COUNT (NS_IN_S / FREQ) + +struct lptmr_regs { + uint32_t csr; // Control Status + uint32_t psr; // Prescaler and Glitch Filter + uint32_t cmr; // Compare + uint32_t cnr; // Counter +}; + +struct lptmr { + volatile struct lptmr_regs *clock_reg; + volatile struct lptmr_regs *timeout_reg; + uint64_t last_count; + uint64_t high_bits; + + /* Current timer info */ + bool periodic; + + ltimer_callback_fn_t cb_fn; + void *cb_token; + + ps_io_ops_t ops; + irq_id_t irq_id[2]; +}; + +static void init(volatile struct lptmr_regs *reg, uint32_t compare) +{ + /* Disable timer before configuring it: */ + reg->csr = 0; + reg->psr = LPTMR_PSR; + reg->cmr = compare; + reg->csr = LPTMR_CSR_TIE | LPTMR_CSR_TFC | LPTMR_CSR_TCF; + /* Do not alter CSR[5:1] when setting TEN: */ + reg->csr |= LPTMR_CSR_TEN; +} + +static int reset(void *data) +{ + struct lptmr *t = data; + + /* IRQ comes one clock later after a match: */ + init(t->clock_reg, ~0); + /* Disable timeout timer, ack any pending IRQ */ + t->timeout_reg->csr = LPTMR_CSR_TCF; + + t->high_bits = 0; + t->last_count = 0; + t->periodic = false; + return 0; +} + +static int get_time(void *data, uint64_t *time) +{ + struct lptmr *t = data; + uint64_t v, s; + uint32_t count; + + /* Write CNR to sync count value: */ + t->clock_reg->cnr = 0; + /* Now read the counter value: */ + count = t->clock_reg->cnr; + + v = t->high_bits | count; + + /* Handle overflow */ + if (v < t->last_count) { + v += 1ull << 32; + } + t->last_count = v; + + /* Convert seconds exactly: */ + s = v / FREQ; + v = v % FREQ; + *time = s * NS_IN_S + v * NS_PER_COUNT; + return 0; +} + +static int set_timeout(void *data, uint64_t ns, timeout_type_t type) +{ + struct lptmr *t = data; + uint64_t now = 0; + uint64_t v, s; + + switch (type) { + case TIMEOUT_PERIODIC: + t->periodic = true; + break; + case TIMEOUT_ABSOLUTE: + t->periodic = false; + get_time(t, &now); + if (now <= ns) { + ns -= now; + } else { + ns = 0; + } + break; + case TIMEOUT_RELATIVE: + t->periodic = false; + break; + default: + return EINVAL; + } + /* Convert seconds exactly: */ + s = ns / NS_IN_S; + ns = ns % NS_IN_S; + /* Convert to counts and round up: */ + v = s * FREQ + (ns + NS_PER_COUNT - 1) / NS_PER_COUNT; + if (v > UINT32_MAX) { + return EINVAL; + } + init(t->timeout_reg, v); + return 0; +} + +static void clock_irq(void *data, ps_irq_acknowledge_fn_t ack_fn, void *ack_data) +{ + struct lptmr *t = data; + + /* Ack the IRQ */ + t->clock_reg->csr |= LPTMR_CSR_TCF; + + /* Increment high bits only here: */ + t->high_bits += 1ull << 32; +} + +static void timeout_irq(void *data, ps_irq_acknowledge_fn_t ack_fn, void *ack_data) +{ + struct lptmr *t = data; + + if (t->periodic) { + /* Ack the IRQ at device level */ + t->timeout_reg->csr |= LPTMR_CSR_TCF; + } else { + /* Stop timer */ + t->timeout_reg->csr = LPTMR_CSR_TCF; + } + /* Ack the IRQ at CPU level */ + ack_fn(ack_data); + + /* Call user callback: */ + if (t->cb_fn) { + t->cb_fn(t->cb_token, LTIMER_TIMEOUT_EVENT); + } +} + +static void destroy(void *data) +{ + struct lptmr *t = data; + + t->clock_reg->csr = 0; + t->timeout_reg->csr = 0; + ps_io_unmap(&t->ops.io_mapper, (void *)t->clock_reg, 4096); + ps_io_unmap(&t->ops.io_mapper, (void *)t->timeout_reg, 4096); + for (int i = 0; i < 2; ++i) { + ps_irq_unregister(&t->ops.irq_ops, t->irq_id[i]); + } + ps_free(&t->ops.malloc_ops, sizeof(*t), (void **)&t); +} + +int ltimer_default_init(ltimer_t *ltimer, ps_io_ops_t ops, ltimer_callback_fn_t cb_fn, void *cb_token) +{ + struct lptmr *t; + int error; + pmem_region_t pmem = {.type = PMEM_TYPE_DEVICE, .length = 4096}; + ps_irq_t irq = {.type = PS_INTERRUPT}; + + error = ps_calloc(&ops.malloc_ops, 1, sizeof(*t), (void **)&t); + if (error) { + return error; + } + pmem.base_addr = LPTMR1_ADDR; + t->clock_reg = ps_pmem_map(&ops, pmem, false, PS_MEM_NORMAL); + if (!t->clock_reg) { + return EIO; + } + pmem.base_addr = LPTMR2_ADDR; + t->timeout_reg = ps_pmem_map(&ops, pmem, false, PS_MEM_NORMAL); + if (!t->timeout_reg) { + return EIO; + } + irq.irq.number = LPTMR1_IRQ; + t->irq_id[0] = ps_irq_register(&ops.irq_ops, irq, clock_irq, t); + if (t->irq_id[0] < 0) { + return EIO; + } + irq.irq.number = LPTMR2_IRQ; + t->irq_id[1] = ps_irq_register(&ops.irq_ops, irq, timeout_irq, t); + if (t->irq_id[1] < 0) { + return EIO; + } + t->ops = ops; + t->cb_fn = cb_fn; + t->cb_token = cb_token; + ltimer->get_time = get_time; + ltimer->set_timeout = set_timeout; + ltimer->reset = reset; + ltimer->destroy = destroy; + ltimer->data = t; + + reset(t); + return 0; +} + +#endif // !Arm Generic Timer