Skip to content

Commit

Permalink
i.MX 93: Add Low Power Timer (LPTMR) Driver
Browse files Browse the repository at this point in the history
Only used if the Arm generic timer cannot be used.

The default is to use the 24 MHz clock source as that is synchonous
with the scheduling clock.

The 24 MHz clock on the MCIMX93-SOMB2 module, which comes with the
MCIMX93-EVK board, seems to have a 2.4% frequency error compared
to the 32 kHz clock, making sel4test fail the SCHED0011 test.

For real use cases using the 32 kHz clock makes more sense. The
clock source is user configurable via CMake.

Retrieving the time takes about 0.5 us, so this timer is very slow.

However, the 32 kHz timer keeps running when the board is in low
power state and can wake up the CPU.

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 <[email protected]>
  • Loading branch information
Indanz committed Dec 4, 2024
1 parent 5e7a0b3 commit 741890a
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 1 deletion.
12 changes: 12 additions & 0 deletions libplatsupport/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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\
32kHz -> High accuracy, but low precision.\
24MHz -> Higher precision, but lower accuracy. Synchronous with the scheduling clock."
"24MHz;LibPlatSupportLPTMRclock32kHz;LIB_PLAT_SUPPORT_LPTMR_CLOCK_24MHz;KernelPlatformIMX93"
"32kHz;LibPlatSupportLPTMRclock24MHz;LIB_PLAT_SUPPORT_LPTMR_CLOCK_32kHZ;KernelPlatformIMX93"
)

mark_as_advanced(CLEAR LibPlatSupportX86ConsoleDevice LibPlatSupportLPTMRclock)

set(LibPlatSupportMach "")
if(KernelPlatformRpi3 OR KernelPlatformRpi4)
set(LibPlatSupportMach "bcm")
Expand Down
2 changes: 1 addition & 1 deletion libplatsupport/include/platsupport/ltimer.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ typedef struct ltimer {

/*
* Get the precision of this time returned by get_time. i.e if the timer is
* millisecond precise return NS_IN_US.
* millisecond precise return NS_IN_MS.
*
* @param data for the logical timer to use
* @param[out] resolution variable to read the resoltion value into
Expand Down
242 changes: 242 additions & 0 deletions libplatsupport/src/plat/imx93/lptmr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/*
* 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 <platsupport/gen_config.h>
#include <platsupport/io.h>
#include <platsupport/irq.h>
#include <platsupport/ltimer.h>
#include <utils/util.h>

/* 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 32kHz
#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 NS_PER_COUNT 31250

#elif defined(CONFIG_LIB_PLAT_SUPPORT_LPTMR_CLOCK_24MHz)
#define LPTMR_PSR (LPTMR_PSR_PCS_24MHz | LPTMR_PSR_PRESCALE(7))
#define NS_PER_COUNT 5333

#else
#error "LPTMR: Unknown clock source"
#endif

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_time;
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_time = 0;
t->periodic = false;
return 0;
}

static int get_time(void *data, uint64_t *time)
{
struct lptmr* t = data;
uint64_t v;
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) * NS_PER_COUNT;

/* Handle overflow */
if (v < t->last_time) {
v += (1ul << 32) * NS_PER_COUNT;
}
t->last_time = v;
*time = v;
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;

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 to counts and round up: */
v = (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 += 1ul << 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

0 comments on commit 741890a

Please sign in to comment.