You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nuttx.apache.org by xi...@apache.org on 2026/09/01 09:41:21 UTC
(nuttx) 01/03: arch/arm/rtl8721dx: add shared Ameba timer driver
This is an automated email from the ASF dual-hosted git repository.
xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 2cc38e43fb89f475058a014ce5c5c3a4e3c63e4c
Author: dechao_gong <de...@realsil.com.cn>
AuthorDate: Tue Jul 21 19:39:03 2026 +0800
arch/arm/rtl8721dx: add shared Ameba timer driver
Add a parameterised NuttX timer lower-half for the Realtek Ameba
general-purpose timers, sitting on the SDK fwlib RTIM register layer and
registered at /dev/timerN. The shared driver
(arch/arm/src/common/ameba/ameba_timer.c) reads a per-chip instance
table (ameba_timer_chip.h) for each timer's base, input clock, RCC gate
masks and IRQ; the period is programmed directly in microseconds and
converted to the 32-bit auto-reload with one clkfreq formula.
On the pke8721daf two of the 32.768 kHz "basic" (LTIM) timers are
exposed: /dev/timer0 is TIM1 and /dev/timer1 is TIM2. TIM0 is left
untouched because the boot ROM claims it as the always-on system timer
(SYSTIMER); reprogramming it would break every SDK delay. The RTIM
time-base entry points resolve to ROM, while the interrupt-clear and
period-change helpers come from the fwlib RAM source ameba_tim.c (shared
with the PWM driver).
Verified on hardware with examples/timer against both devices: the
update interrupt fires at the requested 1 s interval (measured with the
independent ROM SYSTIMER = 32768 ticks = 1.000 s).
Also whitelist the vendor RTIM_ symbol prefix in tools/nxstyle.c,
alongside the existing RCC_/SYSTIMER_ Ameba SDK entries.
Assisted-by: Claude <no...@anthropic.com>
Signed-off-by: dechao_gong <de...@realsil.com.cn>
---
.../arm/rtl8721dx/boards/pke8721daf/index.rst | 16 +
arch/arm/src/common/ameba/Kconfig | 17 +
arch/arm/src/common/ameba/ameba_timer.c | 546 +++++++++++++++++++++
arch/arm/src/common/ameba/ameba_timer.h | 72 +++
arch/arm/src/rtl8721dx/CMakeLists.txt | 4 +
arch/arm/src/rtl8721dx/Make.defs | 4 +
arch/arm/src/rtl8721dx/ameba_board.mk | 11 +-
arch/arm/src/rtl8721dx/ameba_timer_chip.h | 111 +++++
.../rtl8721dx/pke8721daf/configs/timer/defconfig | 47 ++
boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt | 7 +-
boards/arm/rtl8721dx/pke8721daf/src/Makefile | 9 +
.../rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c | 10 +
.../pke8721daf/src/rtl8721dx_pke8721daf.h | 13 +
.../arm/rtl8721dx/pke8721daf/src/rtl8721dx_timer.c | 75 +++
14 files changed, 936 insertions(+), 6 deletions(-)
diff --git a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
index 0e66109df8a..0565b1d1725 100644
--- a/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
+++ b/Documentation/platforms/arm/rtl8721dx/boards/pke8721daf/index.rst
@@ -49,6 +49,8 @@ Supported in this NuttX port:
alarm support, driven directly on the SDK fwlib register layer
* On-chip watchdog exposed as a ``/dev/watchdog0`` character device, driven
directly on the SDK fwlib register layer
+* General-purpose timers exposed as ``/dev/timer0`` and ``/dev/timer1``
+ character devices, driven directly on the SDK fwlib register layer
Buttons and LEDs
================
@@ -199,6 +201,20 @@ which opens the device, sets a timeout, and pings it::
nsh> wdog # run the watchdog example
+timer
+-----
+
+Minimal NSH with the on-chip general-purpose timer driver and the ``timer``
+example enabled (no Wi-Fi). Two 32-bit basic timers clocked at 32.768 kHz are
+registered from the board bring-up
+(``boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_timer.c``) as ``/dev/timer0``
+(TIM1) and ``/dev/timer1`` (TIM2); they have no board wiring (they are
+internal). TIM0 is reserved by the boot ROM as the system timer and is not
+exposed. Exercise a device with the example, which sets an interval and counts
+the update interrupts::
+
+ nsh> timer -d /dev/timer0 # run the timer example on TIM1
+
Wi-Fi
=====
diff --git a/arch/arm/src/common/ameba/Kconfig b/arch/arm/src/common/ameba/Kconfig
index ea74d1435d0..d34a65e2b6d 100644
--- a/arch/arm/src/common/ameba/Kconfig
+++ b/arch/arm/src/common/ameba/Kconfig
@@ -149,4 +149,21 @@ config AMEBA_WDG
enabled, so stop() arms the WDG early interrupt and refreshes the
counter from its handler to inhibit the reset.
+config AMEBA_TIMER
+ bool "Timer"
+ default n
+ select TIMER
+ ---help---
+ Expose the Ameba general-purpose timers as NuttX timer devices at
+ /dev/timerN (start/stop/settimeout/getstatus, plus a periodic
+ expiration callback). The timeout is programmed directly in
+ microseconds.
+
+ The driver (arch/arm/src/common/ameba/ameba_timer.c) is a
+ parameterised lower half sitting on the SDK fwlib RTIM register
+ layer; the board picks which of the twelve on-chip timers to expose
+ via the per-chip instance table. On the pke8721daf /dev/timer0 is
+ TIM1 and /dev/timer1 is TIM2, both 32.768 kHz basic timers. TIM0
+ is reserved by the ROM as the system timer and is not exposed.
+
endmenu # Ameba Peripheral Support
diff --git a/arch/arm/src/common/ameba/ameba_timer.c b/arch/arm/src/common/ameba/ameba_timer.c
new file mode 100644
index 00000000000..a90c2f121ea
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_timer.c
@@ -0,0 +1,546 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_timer.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+/* NuttX timer lower half for the Realtek Ameba general-purpose timers. The
+ * chip carries twelve identical 32-bit up-counting timers (TIM0..TIM11);
+ * this driver is a parameterised lower half that binds any of them, by
+ * an instance index into the per-chip AMEBA_TIMER_CONFIG_TABLE. The board
+ * calls ameba_timer_initialize() once per device, so several timers can be
+ * exposed at once -- on the pke8721daf /dev/timer0 is TIM1 (32.768 kHz, long
+ * low-power intervals) and /dev/timer1 is TIM2 (also 32.768 kHz). TIM0 is
+ * reserved by the ROM as the system timer (SYSTIMER, 31 us/tick) and is
+ * never exposed as a general-purpose timer here.
+ *
+ * Each timer counts up from 0 to its auto-reload value ARR, raises the
+ * update interrupt on overflow and reloads to 0 automatically, so a running
+ * timer is inherently periodic. A timeout in microseconds maps to ARR with
+ * the single SDK formula ARR = us * clkfreq / 1e6 - 1 (verified against the
+ * vendor timer_api.c gtimer_reload for the 32.768 kHz basic timers); the
+ * arithmetic is done in 64 bits so a large microsecond timeout scaled by the
+ * clock does not overflow.
+ *
+ * The timer is driven through the SDK fwlib RTIM API. The time-base setup
+ * (RTIM_TimeBaseStructInit/RTIM_TimeBaseInit/RTIM_Cmd/RTIM_INTConfig/
+ * RTIM_GetCount) resolves to the on-chip ROM symbol table (_LONG_CALL_
+ * entries), while the interrupt-clear and hot period-change helpers
+ * (RTIM_INTClear/RTIM_ChangePeriodImmediate) come from the fwlib RAM source
+ * ameba_tim.c and linked in. The NuttX interrupt is attached with the
+ * standard irq_attach()/up_enable_irq() (RTIM_TimeBaseInit is passed a NULL
+ * user callback so the vendor HAL installs nothing of its own), matching the
+ * other Ameba drivers on this core.
+ *
+ * The chip-specific wiring (per-instance register base, input clock, RCC
+ * clock masks and IRQ number) lives entirely in the per-chip
+ * ameba_timer_chip.h instance table; the shared driver reads only that table
+ * and is never edited per chip. As in the PWM driver the one fwlib init
+ * struct layout used here is mirrored locally rather than pulling in the
+ * vendor <ameba_pwmtimer.h>.
+ */
+
+#include <nuttx/config.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <string.h>
+#include <debug.h>
+
+#include <nuttx/irq.h>
+#include <nuttx/arch.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/timers/timer.h>
+
+#include "ameba_timer.h"
+#include "ameba_timer_chip.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Second/third argument to fwlib "state" style APIs. */
+
+#define AMEBA_DISABLE 0x0
+#define AMEBA_ENABLE 0x1
+
+/* fwlib time-base field values (from ameba_pwmtimer.h). */
+
+#define AMEBA_TIM_IT_UPDATE 0x00000001 /* TIM_IT_Update */
+#define AMEBA_TIM_UPDATESRC_OVER 0x00000004 /* TIM_UpdateSource_Overflow */
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+/* One row of the per-chip AMEBA_TIMER_CONFIG_TABLE. */
+
+struct ameba_timer_config_s
+{
+ uintptr_t base; /* Non-secure timer register base */
+ uint32_t periph; /* RCC periph function mask (arg 1) */
+ uint32_t clk; /* RCC periph clock mask (arg 2) */
+ uint32_t clkfreq; /* Timer input clock (Hz) */
+ int irq; /* NuttX IRQ number */
+ uint8_t idx; /* Timer index (TIM_Idx) */
+};
+
+/* Layout-compatible mirror of the fwlib RTIM_TimeBaseInitTypeDef (same field
+ * order and types); passed by address to RTIM_TimeBaseStructInit()/
+ * RTIM_TimeBaseInit().
+ */
+
+struct ameba_tim_timebase_s
+{
+ uint32_t prescaler; /* TIM_Prescaler */
+ uint32_t period; /* TIM_Period */
+ uint32_t updateevent; /* TIM_UpdateEvent */
+ uint32_t updatesource; /* TIM_UpdateSource */
+ uint32_t arrprotection; /* TIM_ARRProtection */
+ uint8_t idx; /* TIM_Idx */
+ uint32_t securetimer; /* TIM_SecureTimer */
+};
+
+struct ameba_timer_lowerhalf_s
+{
+ const struct timer_ops_s *ops; /* Lower half operations (must be first) */
+ struct ameba_timer_config_s cfg;
+ tccb_t callback; /* Upper-half expiration callback */
+ void *arg; /* Argument for the callback */
+ uint32_t timeout; /* Programmed timeout (microseconds) */
+ uint32_t arr; /* Auto-reload value for that timeout */
+ bool started; /* Time base is running */
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+/* SDK fwlib RTIM/clock API (ROM for the time base, RAM ameba_tim.c for the
+ * interrupt-clear and period-change helpers). The RTIM_TypeDef pointer is
+ * passed as the raw register base cast to void *.
+ */
+
+extern void RCC_PeriphClockCmd(uint32_t periph, uint32_t clock,
+ uint8_t newstate);
+extern void RTIM_TimeBaseStructInit(struct ameba_tim_timebase_s *init);
+extern void RTIM_TimeBaseInit(void *timx,
+ struct ameba_tim_timebase_s *init,
+ int irqnum, void *usercb, uint32_t cbdata);
+extern void RTIM_Cmd(void *timx, uint32_t newstate);
+extern void RTIM_INTConfig(void *timx, uint32_t tim_it, uint32_t newstate);
+extern void RTIM_INTClear(void *timx);
+extern void RTIM_ChangePeriodImmediate(void *timx, uint32_t autoreload);
+extern uint32_t RTIM_GetCount(void *timx);
+
+/* Timer lower-half operations. */
+
+static int ameba_timer_start(struct timer_lowerhalf_s *lower);
+static int ameba_timer_stop(struct timer_lowerhalf_s *lower);
+static int ameba_timer_getstatus(struct timer_lowerhalf_s *lower,
+ struct timer_status_s *status);
+static int ameba_timer_settimeout(struct timer_lowerhalf_s *lower,
+ uint32_t timeout);
+static void ameba_timer_setcallback(struct timer_lowerhalf_s *lower,
+ tccb_t callback, void *arg);
+static int ameba_timer_maxtimeout(struct timer_lowerhalf_s *lower,
+ uint32_t *maxtimeout);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct timer_ops_s g_ameba_timer_ops =
+{
+ .start = ameba_timer_start,
+ .stop = ameba_timer_stop,
+ .getstatus = ameba_timer_getstatus,
+ .settimeout = ameba_timer_settimeout,
+ .setcallback = ameba_timer_setcallback,
+ .maxtimeout = ameba_timer_maxtimeout,
+};
+
+/* Per-chip instance table: { base, periph, clk, clkfreq, irq, idx }. */
+
+static const struct ameba_timer_config_s
+ g_ameba_timer_config[AMEBA_TIMER_NINSTANCES] = AMEBA_TIMER_CONFIG_TABLE;
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_timer_us2arr
+ *
+ * Description:
+ * Convert a timeout in microseconds to the timer's auto-reload value using
+ * the SDK formula ARR = us * clkfreq / 1e6 - 1, in 64-bit arithmetic so a
+ * large microsecond timeout scaled by the clock does not overflow.
+ *
+ ****************************************************************************/
+
+static uint32_t ameba_timer_us2arr(uint32_t clkfreq, uint32_t us)
+{
+ uint64_t ticks = ((uint64_t)us * clkfreq) / 1000000ull;
+
+ if (ticks == 0)
+ {
+ ticks = 1;
+ }
+
+ return (uint32_t)(ticks - 1);
+}
+
+/****************************************************************************
+ * Name: ameba_timer_interrupt
+ *
+ * Description:
+ * Update-event ISR. Clears the flag, invokes the upper-half callback and
+ * honours its verdict: a false return stops the timer, a true return keeps
+ * it periodic and may hot-change the period to the callback's next
+ * interval (deferred by the ARR protection to the reload just started).
+ *
+ ****************************************************************************/
+
+/* Dummy handler passed to RTIM_TimeBaseInit so the ROM performs its full
+ * timer configuration (some register setup is skipped when UserCB is NULL).
+ * NuttX installs its own vector table, so this is never actually invoked.
+ */
+
+static uint32_t ameba_timer_dummy_cb(void *data)
+{
+ UNUSED(data);
+ return 0;
+}
+
+static int ameba_timer_interrupt(int irq, void *context, void *arg)
+{
+ struct ameba_timer_lowerhalf_s *priv = arg;
+
+ /* Clear the pending update flag via the ROM helper (it polls the timer
+ * clock domain to guarantee the write took effect).
+ */
+
+ RTIM_INTClear((void *)priv->cfg.base);
+
+ if (priv->callback != NULL)
+ {
+ uint32_t next = priv->timeout;
+
+ if (priv->callback(&next, priv->arg))
+ {
+ if (next != priv->timeout && next > 0)
+ {
+ /* The callback asked for a different next interval. */
+
+ priv->timeout = next;
+ priv->arr = ameba_timer_us2arr(priv->cfg.clkfreq, next);
+ RTIM_ChangePeriodImmediate((void *)priv->cfg.base, priv->arr);
+ }
+ }
+ else
+ {
+ /* The callback asked to stop after this expiration. */
+
+ RTIM_INTConfig((void *)priv->cfg.base, AMEBA_TIM_IT_UPDATE,
+ AMEBA_DISABLE);
+ RTIM_Cmd((void *)priv->cfg.base, AMEBA_DISABLE);
+ priv->started = false;
+ }
+ }
+
+ /* Clear once more (vendor double-clear guard). */
+
+ RTIM_INTClear((void *)priv->cfg.base);
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_timer_start
+ ****************************************************************************/
+
+static int ameba_timer_start(struct timer_lowerhalf_s *lower)
+{
+ struct ameba_timer_lowerhalf_s *priv =
+ (struct ameba_timer_lowerhalf_s *)lower;
+ struct ameba_tim_timebase_s tb;
+
+ if (priv->started)
+ {
+ return -EPERM;
+ }
+
+ if (priv->arr == 0 && priv->timeout == 0)
+ {
+ /* No timeout has been programmed yet. */
+
+ return -EINVAL;
+ }
+
+ memset(&tb, 0, sizeof(tb));
+ RTIM_TimeBaseStructInit(&tb);
+
+ /* Match the vendor gtimer_init exactly: TIM_Period is NOT supplied here.
+ * TimeBaseInit with ARR preload enabled would leave the auto-reload shadow
+ * at its reset value (0) until the first overflow, so arming the interrupt
+ * immediately floods the CPU. The period is instead programmed by the
+ * RTIM_ChangePeriodImmediate() below, which clears ARPE, writes ARR and
+ * issues a UG so the shadow is loaded before the timer runs.
+ */
+
+ tb.idx = priv->cfg.idx;
+ tb.updateevent = AMEBA_ENABLE;
+ tb.updatesource = AMEBA_TIM_UPDATESRC_OVER;
+ tb.arrprotection = AMEBA_ENABLE;
+
+ /* NULL user callback: the NuttX ISR is attached separately, so the vendor
+ * HAL installs no handler of its own.
+ */
+
+ RTIM_TimeBaseInit((void *)priv->cfg.base, &tb, priv->cfg.irq,
+ (void *)ameba_timer_dummy_cb, 0);
+
+ /* Program the period and force an immediate shadow reload (vendor
+ * gtimer_reload). The overflow reload is periodic on this IP whatever the
+ * ARR-preload bit is, so no further CR fix-up is needed here.
+ */
+
+ RTIM_ChangePeriodImmediate((void *)priv->cfg.base, priv->arr);
+
+ /* Only fire the update interrupt when an upper-half callback wants it; a
+ * bare timer just free-runs and reloads for getstatus() polling (vendor
+ * gtimer_start: INTConfig then Cmd).
+ */
+
+ RTIM_INTConfig((void *)priv->cfg.base, AMEBA_TIM_IT_UPDATE,
+ priv->callback != NULL ? AMEBA_ENABLE : AMEBA_DISABLE);
+
+ RTIM_Cmd((void *)priv->cfg.base, AMEBA_ENABLE);
+
+ priv->started = true;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_timer_stop
+ ****************************************************************************/
+
+static int ameba_timer_stop(struct timer_lowerhalf_s *lower)
+{
+ struct ameba_timer_lowerhalf_s *priv =
+ (struct ameba_timer_lowerhalf_s *)lower;
+
+ if (!priv->started)
+ {
+ return -EPERM;
+ }
+
+ RTIM_INTConfig((void *)priv->cfg.base, AMEBA_TIM_IT_UPDATE, AMEBA_DISABLE);
+ RTIM_Cmd((void *)priv->cfg.base, AMEBA_DISABLE);
+ priv->started = false;
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_timer_getstatus
+ ****************************************************************************/
+
+static int ameba_timer_getstatus(struct timer_lowerhalf_s *lower,
+ struct timer_status_s *status)
+{
+ struct ameba_timer_lowerhalf_s *priv =
+ (struct ameba_timer_lowerhalf_s *)lower;
+ uint32_t count;
+ uint32_t remaining;
+
+ status->flags = 0;
+ if (priv->started)
+ {
+ status->flags |= TCFLAGS_ACTIVE;
+ }
+
+ if (priv->callback != NULL)
+ {
+ status->flags |= TCFLAGS_HANDLER;
+ }
+
+ status->timeout = priv->timeout;
+
+ /* Time left = (ARR + 1 - current up-count) converted back to microseconds
+ * in 64-bit arithmetic; a stopped timer has the full timeout left.
+ */
+
+ if (priv->started)
+ {
+ count = RTIM_GetCount((void *)priv->cfg.base);
+ remaining = (count > priv->arr) ? 0 : (priv->arr + 1 - count);
+ status->timeleft =
+ (uint32_t)(((uint64_t)remaining * 1000000ull) / priv->cfg.clkfreq);
+ }
+ else
+ {
+ status->timeleft = priv->timeout;
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_timer_settimeout
+ ****************************************************************************/
+
+static int ameba_timer_settimeout(struct timer_lowerhalf_s *lower,
+ uint32_t timeout)
+{
+ struct ameba_timer_lowerhalf_s *priv =
+ (struct ameba_timer_lowerhalf_s *)lower;
+
+ if (timeout == 0)
+ {
+ return -EINVAL;
+ }
+
+ priv->timeout = timeout;
+ priv->arr = ameba_timer_us2arr(priv->cfg.clkfreq, timeout);
+
+ /* Hot-update a running time base; the auto-reload protection defers the
+ * change to the next update event so the count does not glitch.
+ */
+
+ if (priv->started)
+ {
+ RTIM_ChangePeriodImmediate((void *)priv->cfg.base, priv->arr);
+ }
+
+ return OK;
+}
+
+/****************************************************************************
+ * Name: ameba_timer_setcallback
+ ****************************************************************************/
+
+static void ameba_timer_setcallback(struct timer_lowerhalf_s *lower,
+ tccb_t callback, void *arg)
+{
+ struct ameba_timer_lowerhalf_s *priv =
+ (struct ameba_timer_lowerhalf_s *)lower;
+ irqstate_t flags;
+
+ flags = enter_critical_section();
+
+ priv->callback = callback;
+ priv->arg = arg;
+
+ /* Track the interrupt enable to the callback while the timer runs, so a
+ * callback set or cleared after start() takes effect immediately.
+ */
+
+ if (priv->started)
+ {
+ RTIM_INTConfig((void *)priv->cfg.base, AMEBA_TIM_IT_UPDATE,
+ callback != NULL ? AMEBA_ENABLE : AMEBA_DISABLE);
+ }
+
+ leave_critical_section(flags);
+}
+
+/****************************************************************************
+ * Name: ameba_timer_maxtimeout
+ ****************************************************************************/
+
+static int ameba_timer_maxtimeout(struct timer_lowerhalf_s *lower,
+ uint32_t *maxtimeout)
+{
+ struct ameba_timer_lowerhalf_s *priv =
+ (struct ameba_timer_lowerhalf_s *)lower;
+ uint64_t maxus;
+
+ /* Largest microsecond value whose ARR still fits the 32-bit counter:
+ * us = (ARR_max + 1) * 1e6 / clkfreq, capped to the uint32_t API range.
+ */
+
+ maxus = ((uint64_t)0xffffffffull * 1000000ull) / priv->cfg.clkfreq;
+ if (maxus > 0xffffffffull)
+ {
+ maxus = 0xffffffffull;
+ }
+
+ *maxtimeout = (uint32_t)maxus;
+ return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: ameba_timer_initialize
+ *
+ * Description:
+ * See ameba_timer.h.
+ *
+ ****************************************************************************/
+
+int ameba_timer_initialize(const char *devpath, int instance)
+{
+ struct ameba_timer_lowerhalf_s *priv;
+ void *handle;
+
+ if (instance < 0 || instance >= AMEBA_TIMER_NINSTANCES)
+ {
+ return -EINVAL;
+ }
+
+ priv = kmm_zalloc(sizeof(struct ameba_timer_lowerhalf_s));
+ if (priv == NULL)
+ {
+ return -ENOMEM;
+ }
+
+ priv->ops = &g_ameba_timer_ops;
+ priv->cfg = g_ameba_timer_config[instance];
+
+ /* Gate the timer's peripheral clock (function and clock masks are equal on
+ * this chip) and wire the NuttX interrupt. The time base itself is left
+ * stopped until start(); settimeout() supplies the period first.
+ */
+
+ RCC_PeriphClockCmd(priv->cfg.periph, priv->cfg.clk, AMEBA_ENABLE);
+
+ irq_attach(priv->cfg.irq, ameba_timer_interrupt, priv);
+ up_enable_irq(priv->cfg.irq);
+
+ handle = timer_register(devpath, (struct timer_lowerhalf_s *)priv);
+ if (handle == NULL)
+ {
+ up_disable_irq(priv->cfg.irq);
+ irq_detach(priv->cfg.irq);
+ _err("ERROR: timer_register(%s) failed\n", devpath);
+ kmm_free(priv);
+ return -EEXIST;
+ }
+
+ return OK;
+}
diff --git a/arch/arm/src/common/ameba/ameba_timer.h b/arch/arm/src/common/ameba/ameba_timer.h
new file mode 100644
index 00000000000..bec1e529483
--- /dev/null
+++ b/arch/arm/src/common/ameba/ameba_timer.h
@@ -0,0 +1,72 @@
+/****************************************************************************
+ * arch/arm/src/common/ameba/ameba_timer.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_TIMER_H
+#define __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_TIMER_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+#ifdef __cplusplus
+#define EXTERN extern "C"
+extern "C"
+{
+#else
+#define EXTERN extern
+#endif
+
+/****************************************************************************
+ * Name: ameba_timer_initialize
+ *
+ * Description:
+ * Bind one Ameba general-purpose timer instance to the NuttX timer
+ * character driver and register it at the given path (typically
+ * "/dev/timerN"). The instance index selects a row of the per-chip
+ * AMEBA_TIMER_CONFIG_TABLE (0 == TIM1, 1 == TIM2, both @ 32.768 kHz on the
+ * pke8721daf); its base, clock, RCC masks and IRQ come from that table, so
+ * a board only names the device and the instance it wants.
+ *
+ * Input Parameters:
+ * devpath - The device path to register, e.g. "/dev/timer0".
+ * instance - Index into the per-chip timer instance table
+ * (0 .. AMEBA_TIMER_NINSTANCES - 1).
+ *
+ * Returned Value:
+ * Zero (OK) on success; a negated errno value on failure.
+ *
+ ****************************************************************************/
+
+int ameba_timer_initialize(const char *devpath, int instance);
+
+#undef EXTERN
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ARCH_ARM_SRC_COMMON_AMEBA_AMEBA_TIMER_H */
diff --git a/arch/arm/src/rtl8721dx/CMakeLists.txt b/arch/arm/src/rtl8721dx/CMakeLists.txt
index 5e2dda71e14..b9345e0d78f 100644
--- a/arch/arm/src/rtl8721dx/CMakeLists.txt
+++ b/arch/arm/src/rtl8721dx/CMakeLists.txt
@@ -72,6 +72,10 @@ if(CONFIG_AMEBA_WDG)
list(APPEND SRCS ${AMEBA_COMMON}/ameba_wdg.c)
endif()
+if(CONFIG_AMEBA_TIMER)
+ list(APPEND SRCS ${AMEBA_COMMON}/ameba_timer.c)
+endif()
+
target_include_directories(arch PRIVATE ${AMEBA_COMMON})
target_sources(arch PRIVATE ${SRCS})
diff --git a/arch/arm/src/rtl8721dx/Make.defs b/arch/arm/src/rtl8721dx/Make.defs
index 0503fce09fb..303d1fc2b82 100644
--- a/arch/arm/src/rtl8721dx/Make.defs
+++ b/arch/arm/src/rtl8721dx/Make.defs
@@ -82,6 +82,10 @@ ifeq ($(CONFIG_AMEBA_WDG),y)
CHIP_CSRCS += ameba_wdg.c
endif
+ifeq ($(CONFIG_AMEBA_TIMER),y)
+CHIP_CSRCS += ameba_timer.c
+endif
+
############################################################################
# Realtek RTL8721Dx SDK integration
#
diff --git a/arch/arm/src/rtl8721dx/ameba_board.mk b/arch/arm/src/rtl8721dx/ameba_board.mk
index 204699ed8d7..e2710e0eea4 100644
--- a/arch/arm/src/rtl8721dx/ameba_board.mk
+++ b/arch/arm/src/rtl8721dx/ameba_board.mk
@@ -155,12 +155,13 @@ ifeq ($(CONFIG_AMEBA_SPI),y)
AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_spi.c
endif
-# PWM timer register layer. The time-base entry points (RTIM_TimeBaseInit/
-# StructInit/Cmd) are in ROM, but the compare/period helpers the PWM driver
-# (arch/.../common/ameba/ameba_pwm.c) calls -- RTIM_CCStructInit/CCxInit/
-# CCRxSet/CCxCmd/ChangePeriod/PrescalerConfig -- are compiled from this RAM
+# PWM/timer register layer. The time-base entry points (RTIM_TimeBaseInit/
+# StructInit/Cmd/INTConfig/GetCount) are in ROM, but the compare/period and
+# interrupt-clear helpers the PWM driver (ameba_pwm.c: RTIM_CCStructInit/
+# CCxInit/CCRxSet/CCxCmd/ChangePeriod/PrescalerConfig) and the timer driver
+# (ameba_timer.c: RTIM_INTClear/ChangePeriod) call are compiled from this RAM
# source and linked in (--gc-sections drops the unused input-capture paths).
-ifeq ($(CONFIG_AMEBA_PWM),y)
+ifneq (,$(filter y,$(CONFIG_AMEBA_PWM) $(CONFIG_AMEBA_TIMER)))
AMEBA_FWLIB_SRCS += $(AMEBA_SOC)/fwlib/ram_common/ameba_tim.c
endif
diff --git a/arch/arm/src/rtl8721dx/ameba_timer_chip.h b/arch/arm/src/rtl8721dx/ameba_timer_chip.h
new file mode 100644
index 00000000000..90e65e096c5
--- /dev/null
+++ b/arch/arm/src/rtl8721dx/ameba_timer_chip.h
@@ -0,0 +1,111 @@
+/****************************************************************************
+ * arch/arm/src/rtl8721dx/ameba_timer_chip.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_ARM_SRC_RTL8721DX_AMEBA_TIMER_CHIP_H
+#define __ARCH_ARM_SRC_RTL8721DX_AMEBA_TIMER_CHIP_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+#include <nuttx/irq.h>
+
+#include <stdint.h>
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Per-chip general-purpose timer wiring for RTL8721DX (amebadplus). The
+ * shared driver (arch/arm/src/common/ameba/ameba_timer.c) includes this
+ * header to learn which timer instances it may expose and, for each one, its
+ * register base, input clock, RCC clock masks and interrupt line. A port to
+ * another Ameba chip supplies a same-named header on the chip include path;
+ * the shared driver is never edited -- it reads only the macros below and
+ * the instance table, computing the period from clkfreq with one formula.
+ *
+ * The Ameba SoC carries twelve timers (TIM0..TIM11) that all share the one
+ * RTIM register layout and fwlib API, so the register access, the
+ * microsecond<->tick conversion and the whole lower-half live in the shared
+ * driver. What differs per instance -- and per chip -- is only data: the
+ * base address, the clock that feeds it, its RCC gate masks and its NuttX
+ * IRQ number. These sit in AMEBA_TIMER_CONFIG_TABLE so a new chip (or
+ * a different instance choice) edits this header alone.
+ *
+ * This port exposes two of the twelve, both from the 32-bit "basic" (LTIM)
+ * bank so a single lower-half serves them uniformly (verified against the
+ * SoC hal_platform.h / sysreg_lsys.h / vector table and the vendor
+ * timer_api.c, not guessed):
+ *
+ * instance timer base clock RCC gate NuttX IRQ
+ * -------- ------ ---------- --------- -------------- ----------
+ * 0 TIM1 0x41017200 32.768 kHz APBPeriph_LTIM1 IRQ_TIMER1
+ * 1 TIM2 0x41017400 32.768 kHz APBPeriph_LTIM2 IRQ_TIMER2
+ *
+ * TIM1 and TIM2 are two of the eight 32-bit "basic" (LTIM) timers clocked
+ * from the 32.768 kHz LS clock: ~30.5 us per tick, up to ~36 h in one shot.
+ * A full 32-bit auto-reload lets them cover the whole microsecond API range,
+ * which is why the driver exposes this bank rather than the four 40 MHz
+ * "high" (HTIM) timers TIM8..TIM11: those have only a 16-bit auto-reload
+ * (max ~1.638 ms at 40 MHz) and no usable prescaler on TIM10/TIM11, so they
+ * suit sub-millisecond fine timing only and are intentionally not exposed.
+ * TIM8/TIM9 additionally drive the PWM/input-capture block.
+ *
+ * TIM0 is deliberately NOT exposed: the boot ROM claims it as the always-on
+ * system timer (see the vendor ameba_delay.h -- "TIM0 is used as systimer,
+ * so TIM0 can not be used for other purpose ... init when boot in rom
+ * code"), and DelayUs/DelayMs/SYSTIMER_* all rely on it. Reprogramming TIM0
+ * would break every SDK delay, so the driver starts the basic bank at TIM1.
+ *
+ * The RCC gate masks are the fwlib APBPeriph_LTIM1 / APBPeriph_LTIM2 values
+ * (function and clock masks are identical on this chip). They are written
+ * out here rather than pulled from <sysreg_lsys.h> to keep the vendor
+ * headers out of the NuttX include world (same rule as the PWM chip header).
+ */
+
+/* RCC "function" and "clock" masks, bit30 group selector then bit index. */
+
+#define AMEBA_TIMER_LTIM1_MASK (((uint32_t)1 << 30) | ((uint32_t)1 << 13))
+#define AMEBA_TIMER_LTIM2_MASK (((uint32_t)1 << 30) | ((uint32_t)1 << 14))
+
+/* Number of timer instances this chip exposes and the data table that
+ * describes each one. The shared driver defines struct ameba_timer_config_s
+ * and instantiates this table; each row is
+ * { base, periph, clk, clkfreq, irq, idx }.
+ */
+
+#define AMEBA_TIMER_NINSTANCES 2
+
+#define AMEBA_TIMER_CONFIG_TABLE \
+{ \
+ { \
+ 0x41017200ul, AMEBA_TIMER_LTIM1_MASK, AMEBA_TIMER_LTIM1_MASK, \
+ 32768ul, RTL8721DX_IRQ_TIMER1, 1 \
+ }, \
+ { \
+ 0x41017400ul, AMEBA_TIMER_LTIM2_MASK, AMEBA_TIMER_LTIM2_MASK, \
+ 32768ul, RTL8721DX_IRQ_TIMER2, 2 \
+ }, \
+}
+
+#endif /* __ARCH_ARM_SRC_RTL8721DX_AMEBA_TIMER_CHIP_H */
diff --git a/boards/arm/rtl8721dx/pke8721daf/configs/timer/defconfig b/boards/arm/rtl8721dx/pke8721daf/configs/timer/defconfig
new file mode 100644
index 00000000000..22a609302de
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/configs/timer/defconfig
@@ -0,0 +1,47 @@
+#
+# This file is autogenerated: PLEASE DO NOT EDIT IT.
+#
+# You can use "make menuconfig" to make any modifications to the installed .config file.
+# You can then do "make savedefconfig" to generate a new defconfig file that includes your
+# modifications.
+#
+CONFIG_AMEBA_TIMER=y
+CONFIG_ARCH="arm"
+CONFIG_ARCH_BOARD="pke8721daf"
+CONFIG_ARCH_BOARD_PKE8721DAF=y
+CONFIG_ARCH_CHIP="rtl8721dx"
+CONFIG_ARCH_CHIP_RTL8721DX=y
+CONFIG_ARCH_INTERRUPTSTACK=2048
+CONFIG_ARCH_STACKDUMP=y
+CONFIG_ARMV8M_SYSTICK=y
+CONFIG_BOARD_LOOPSPERMSEC=43103
+CONFIG_BUILTIN=y
+CONFIG_DEBUG_ASSERTIONS=y
+CONFIG_DEBUG_FEATURES=y
+CONFIG_DEBUG_FULLOPT=y
+CONFIG_DEBUG_SYMBOLS=y
+CONFIG_DEFAULT_TASK_STACKSIZE=4096
+CONFIG_EXAMPLES_HELLO=y
+CONFIG_EXAMPLES_TIMER=y
+CONFIG_FS_PROCFS=y
+CONFIG_FS_TMPFS=y
+CONFIG_IDLETHREAD_STACKSIZE=4096
+CONFIG_INIT_ENTRYPOINT="nsh_main"
+CONFIG_LIBC_MEMFD_ERROR=y
+CONFIG_MM_DEFAULT_ALIGNMENT=32
+CONFIG_NSH_BUILTIN_APPS=y
+CONFIG_NSH_FILEIOSIZE=512
+CONFIG_NSH_READLINE=y
+CONFIG_PREALLOC_TIMERS=4
+CONFIG_RAM_SIZE=294912
+CONFIG_RAM_START=0x20020000
+CONFIG_RR_INTERVAL=200
+CONFIG_RTL8721DX_FLASH_FS=y
+CONFIG_SCHED_HPWORK=y
+CONFIG_SCHED_HPWORKPRIORITY=192
+CONFIG_SCHED_LPWORK=y
+CONFIG_STACK_COLORATION=y
+CONFIG_SYSTEM_NSH=y
+CONFIG_SYSTEM_NSH_STACKSIZE=2500
+CONFIG_TIMER_ARCH=y
+CONFIG_USEC_PER_TICK=1000
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
index b0e12365ffe..6e4a7c921b5 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
+++ b/boards/arm/rtl8721dx/pke8721daf/src/CMakeLists.txt
@@ -54,6 +54,10 @@ if(CONFIG_AMEBA_WDG)
list(APPEND SRCS rtl8721dx_wdg.c)
endif()
+if(CONFIG_AMEBA_TIMER)
+ list(APPEND SRCS rtl8721dx_timer.c)
+endif()
+
target_sources(board PRIVATE ${SRCS})
if(CONFIG_AMEBA_GPIO
@@ -63,7 +67,8 @@ if(CONFIG_AMEBA_GPIO
OR CONFIG_AMEBA_PWM
OR CONFIG_AMEBA_ADC
OR CONFIG_AMEBA_RTC
- OR CONFIG_AMEBA_WDG)
+ OR CONFIG_AMEBA_WDG
+ OR CONFIG_AMEBA_TIMER)
# The board pin/UART tables pull in the shared driver's public headers from
# arch/arm/src/common/ameba/, not on the default board include path.
target_include_directories(board
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/Makefile b/boards/arm/rtl8721dx/pke8721daf/src/Makefile
index 2fd83e815f3..f5ab3251993 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/Makefile
+++ b/boards/arm/rtl8721dx/pke8721daf/src/Makefile
@@ -96,4 +96,13 @@ CSRCS += rtl8721dx_wdg.c
CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba
endif
+ifeq ($(CONFIG_AMEBA_TIMER),y)
+CSRCS += rtl8721dx_timer.c
+
+# The board timer bring-up pulls in the shared driver's public header from
+# arch/arm/src/common/ameba/, which is not on the default board include path.
+
+CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)arch$(DELIM)arm$(DELIM)src$(DELIM)common$(DELIM)ameba
+endif
+
include $(TOPDIR)/boards/Board.mk
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
index d32f19c2062..5a456a7ea69 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_bringup.c
@@ -188,6 +188,16 @@ int rtl8721dx_bringup(void)
}
#endif
+#ifdef CONFIG_AMEBA_TIMER
+ /* Register the board's timers at /dev/timer0 and /dev/timer1. */
+
+ ret = rtl8721dx_timer_initialize();
+ if (ret < 0)
+ {
+ syslog(LOG_ERR, "ERROR: rtl8721dx_timer_initialize failed: %d\n", ret);
+ }
+#endif
+
/* Install the inter-core HW IPC-semaphore RTOS hooks LAST -- after all the
* flash / WHC bring-up above, and just before this (board_late_initialize)
* path returns and nx_start() hands off to the init task.
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
index 94a3934b73d..6e5b9089410 100644
--- a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_pke8721daf.h
@@ -173,6 +173,19 @@ int rtl8721dx_rtc_initialize(void);
int rtl8721dx_wdg_initialize(void);
#endif
+#ifdef CONFIG_AMEBA_TIMER
+/****************************************************************************
+ * Name: rtl8721dx_timer_initialize
+ *
+ * Description:
+ * Register the board's timers at /dev/timer0 (TIM1) and /dev/timer1
+ * (TIM2) (boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_timer.c).
+ *
+ ****************************************************************************/
+
+int rtl8721dx_timer_initialize(void);
+#endif
+
#ifdef CONFIG_RTL8721DX_FLASH_FS
/****************************************************************************
* Name: ameba_flash_fs_initialize
diff --git a/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_timer.c b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_timer.c
new file mode 100644
index 00000000000..36cf349d25c
--- /dev/null
+++ b/boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_timer.c
@@ -0,0 +1,75 @@
+/****************************************************************************
+ * boards/arm/rtl8721dx/pke8721daf/src/rtl8721dx_timer.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership. The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <syslog.h>
+#include <errno.h>
+
+#include "ameba_timer.h"
+#include "rtl8721dx_pke8721daf.h"
+
+#ifdef CONFIG_AMEBA_TIMER
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: rtl8721dx_timer_initialize
+ *
+ * Description:
+ * Register the board's general-purpose timers. Both timers are internal
+ * (no board wiring), so this just binds the two exposed instances to their
+ * device nodes: /dev/timer0 is TIM1 and /dev/timer1 is TIM2, both 32-bit
+ * basic timers at 32.768 kHz. TIM0 is reserved by the ROM as the system
+ * timer and is not exposed. A failure on either is logged but does not
+ * abort the other.
+ *
+ ****************************************************************************/
+
+int rtl8721dx_timer_initialize(void)
+{
+ int ret;
+
+ ret = ameba_timer_initialize("/dev/timer0", 0);
+ if (ret < 0)
+ {
+ syslog(LOG_ERR,
+ "ERROR: ameba_timer_initialize(/dev/timer0) failed: %d\n", ret);
+ }
+
+ ret = ameba_timer_initialize("/dev/timer1", 1);
+ if (ret < 0)
+ {
+ syslog(LOG_ERR,
+ "ERROR: ameba_timer_initialize(/dev/timer1) failed: %d\n", ret);
+ }
+
+ return ret;
+}
+
+#endif /* CONFIG_AMEBA_TIMER */