forked from nuttx/nuttx-update
timers: add goldfish timer support
Signed-off-by: ligd <liguiding1@xiaomi.com>
This commit is contained in:
parent
0b3859521a
commit
39bfdb95e1
6 changed files with 455 additions and 0 deletions
143
Documentation/platforms/arm/goldfish/goldfish_timer.rst
Normal file
143
Documentation/platforms/arm/goldfish/goldfish_timer.rst
Normal file
|
@ -0,0 +1,143 @@
|
|||
===============
|
||||
GOLDFISH TIMER
|
||||
===============
|
||||
|
||||
Introduction
|
||||
============
|
||||
This device is used to return the current host time as a high-precision signed 64-bit nanosecond value to the kernel, with the starting point being an arbitrary loose reference point. The value should correspond to the QEMU "vm_clock"; that is, it should not be updated when the emulated system is not running, hence it cannot be directly based on the host clock.
|
||||
|
||||
Timer Registers
|
||||
===============
|
||||
|
||||
::
|
||||
|
||||
#define GOLDFISH_TIMER_TIME_LOW 0x0 /* Get current time, then return low-order 32-bits. */
|
||||
#define GOLDFISH_TIMER_TIME_HIGH 0x4 /* Return high 32-bits from previous TIME_LOW read. */
|
||||
#define GOLDFISH_TIMER_ALARM_LOW 0x8 /* Set low 32-bit value of alarm, then arm it. */
|
||||
#define GOLDFISH_TIMER_ALARM_HIGH 0xc /* Set high 32-bit value of alarm. */
|
||||
#define GOLDFISH_TIMER_IRQ_ENABLED 0x10 /* Enable interrupts. */
|
||||
#define GOLDFISH_TIMER_CLEAR_ALARM 0x14 /* Clear alarm. */
|
||||
#define GOLDFISH_TIMER_ALARM_STATUS 0x18 /* Return 1 if alarm is armed, 0 if not. */
|
||||
#define GOLDFISH_TIMER_CLEAR_INTERRUPT 0x1c /* Clear interrupt. */
|
||||
|
||||
Timer Read
|
||||
==========
|
||||
To read the current time, the kernel must execute an IO_READ(TIME_LOW), which returns an unsigned 32-bit value, followed by an IO_READ(TIME_HIGH), which returns a signed 32-bit value corresponding to the high half of the complete value.
|
||||
|
||||
::
|
||||
|
||||
static int goldfish_timer_current(FAR struct oneshot_lowerhalf_s *lower_,
|
||||
FAR struct timespec *ts)
|
||||
{
|
||||
FAR struct goldfish_timer_lowerhalf_s *lower =
|
||||
(FAR struct goldfish_timer_lowerhalf_s *)lower_;
|
||||
irqstate_t flags;
|
||||
uint32_t l32;
|
||||
uint32_t h32;
|
||||
uint64_t nsec;
|
||||
|
||||
DEBUGASSERT(lower != NULL);
|
||||
|
||||
flags = spin_lock_irqsave(&lower->lock);
|
||||
|
||||
l32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_LOW);
|
||||
h32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_HIGH);
|
||||
nsec = ((uint64_t)h32 << 32) | l32;
|
||||
|
||||
ts->tv_sec = nsec / NSEC_PER_SEC;
|
||||
ts->tv_nsec = nsec % NSEC_PER_SEC;
|
||||
|
||||
spin_unlock_irqrestore(&lower->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Timer Set Alarm
|
||||
===============
|
||||
This device can also be used to set an alarm, as follows:
|
||||
|
||||
::
|
||||
|
||||
IO_WRITE(ALARM_HIGH, <high-value>) // Must be executed first.
|
||||
IO_WRITE(ALARM_LOW, <low-value>) // Must be executed second.
|
||||
|
||||
When the corresponding value is reached, the device will raise its IRQ. Note that if the alarm value is already older than the current time, the IRQ will be triggered immediately after the second IO_WRITE().
|
||||
|
||||
::
|
||||
|
||||
static int goldfish_timer_start(FAR struct oneshot_lowerhalf_s *lower_,
|
||||
FAR oneshot_callback_t callback,
|
||||
FAR void *arg,
|
||||
FAR const struct timespec *ts)
|
||||
{
|
||||
FAR struct goldfish_timer_lowerhalf_s *lower =
|
||||
(FAR struct goldfish_timer_lowerhalf_s *)lower_;
|
||||
irqstate_t flags;
|
||||
uint64_t nsec;
|
||||
uint32_t l32;
|
||||
uint32_t h32;
|
||||
|
||||
DEBUGASSERT(lower != NULL);
|
||||
|
||||
flags = spin_lock_irqsave(&lower->lock);
|
||||
|
||||
lower->callback = callback;
|
||||
lower->arg = arg;
|
||||
|
||||
nsec = ts->tv_sec * 1000000000 + ts->tv_nsec;
|
||||
l32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_LOW);
|
||||
h32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_HIGH);
|
||||
nsec += ((uint64_t)h32 << 32) | l32;
|
||||
|
||||
putreg32(1, lower->base + GOLDFISH_TIMER_IRQ_ENABLED);
|
||||
putreg32(nsec >> 32, lower->base + GOLDFISH_TIMER_ALARM_HIGH);
|
||||
putreg32(nsec, lower->base + GOLDFISH_TIMER_ALARM_LOW);
|
||||
|
||||
spin_unlock_irqrestore(&lower->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Timer Interrupt
|
||||
===============
|
||||
IO_WRITE(CLEAR_INTERRUPT, <any>) can be used to lower the IRQ level after the kernel has processed the alarm.
|
||||
IO_WRITE(CLEAR_ALARM, <any>) can be used to disarm the current alarm (if one exists).
|
||||
|
||||
Note: Currently, the alarm is used only on ARM-based systems. On MIPS-based systems, only TIME_LOW / TIME_HIGH are used.
|
||||
|
||||
::
|
||||
|
||||
static int goldfish_timer_interrupt(int irq,
|
||||
FAR void *context,
|
||||
FAR void *arg)
|
||||
{
|
||||
FAR struct goldfish_timer_lowerhalf_s *lower = arg;
|
||||
oneshot_callback_t callback = NULL;
|
||||
irqstate_t flags;
|
||||
void *cbarg;
|
||||
|
||||
DEBUGASSERT(lower != NULL);
|
||||
|
||||
flags = spin_lock_irqsave(&lower->lock);
|
||||
|
||||
putreg32(1, lower->base + GOLDFISH_TIMER_CLEAR_ALARM);
|
||||
|
||||
if (lower->callback != NULL)
|
||||
{
|
||||
callback = lower->callback;
|
||||
cbarg = lower->arg;
|
||||
lower->callback = NULL;
|
||||
lower->arg = NULL;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&lower->lock, flags);
|
||||
|
||||
/* Then perform the callback */
|
||||
|
||||
if (callback)
|
||||
{
|
||||
callback(&lower->lh, cbarg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -2,5 +2,10 @@
|
|||
GOLDFISH
|
||||
========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
goldfish_timer.rst
|
||||
|
||||
Supported Boards
|
||||
================
|
||||
|
|
|
@ -515,4 +515,13 @@ config CS2100CP_REGDEBUG
|
|||
depends on DEBUG_FEATURES
|
||||
|
||||
endif # TIMERS_CS2100CP
|
||||
|
||||
config GOLDFISH_TIMER
|
||||
bool "Enable GOLDFISH_TIMER"
|
||||
default n
|
||||
---help---
|
||||
Goldfish timer is a virtual timer device that is used in the Android
|
||||
emulator. It is used to provide a virtual timer to the guest OS.
|
||||
See: https://android.googlesource.com/platform/external/qemu/+/master/docs/GOLDFISH-VIRTUAL-HARDWARE.TXT
|
||||
|
||||
endmenu # Timer Driver Support
|
||||
|
|
|
@ -116,6 +116,12 @@ ifeq ($(CONFIG_CAPTURE),y)
|
|||
TMRVPATH = :timers
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_GOLDFISH_TIMER),y)
|
||||
CSRCS += goldfish_timer.c
|
||||
TMRDEPPATH = --dep-path timers
|
||||
TMRVPATH = :timers
|
||||
endif
|
||||
|
||||
# Include timer build support (if any were selected)
|
||||
|
||||
DEPPATH += $(TMRDEPPATH)
|
||||
|
|
253
drivers/timers/goldfish_timer.c
Normal file
253
drivers/timers/goldfish_timer.c
Normal file
|
@ -0,0 +1,253 @@
|
|||
/****************************************************************************
|
||||
* drivers/timers/goldfish_timer.c
|
||||
*
|
||||
* 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 <nuttx/arch.h>
|
||||
#include <nuttx/irq.h>
|
||||
#include <nuttx/kmalloc.h>
|
||||
#include <nuttx/timers/oneshot.h>
|
||||
|
||||
/****************************************************************************
|
||||
* Pre-processor Definitions
|
||||
****************************************************************************/
|
||||
|
||||
#ifndef getreg32
|
||||
#define getreg32(a) (*(volatile uint32_t *)(a))
|
||||
#endif
|
||||
|
||||
#ifndef putreg32
|
||||
#define putreg32(v,a) (*(volatile uint32_t *)(a) = (v))
|
||||
#endif
|
||||
|
||||
#define GOLDFISH_TIMER_TIME_LOW 0x0 /* Get current time, then return low-order 32-bits. */
|
||||
#define GOLDFISH_TIMER_TIME_HIGH 0x4 /* Return high 32-bits from previous TIME_LOW read. */
|
||||
#define GOLDFISH_TIMER_ALARM_LOW 0x8 /* Set low 32-bit value of alarm, then arm it. */
|
||||
#define GOLDFISH_TIMER_ALARM_HIGH 0xc /* Set high 32-bit value of alarm. */
|
||||
#define GOLDFISH_TIMER_IRQ_ENABLED 0x10 /* Enable interrupts. */
|
||||
#define GOLDFISH_TIMER_CLEAR_ALARM 0x14 /* Clear alarm. */
|
||||
#define GOLDFISH_TIMER_ALARM_STATUS 0x18 /* Return 1 if alarm is armed, 0 if not. */
|
||||
#define GOLDFISH_TIMER_CLEAR_INTERRUPT 0x1c /* Clear interrupt. */
|
||||
|
||||
/****************************************************************************
|
||||
* Private Types
|
||||
****************************************************************************/
|
||||
|
||||
/* This structure provides the private representation of the "lower-half"
|
||||
* driver state structure. This structure must be cast-compatible with the
|
||||
* oneshot_lowerhalf_s structure.
|
||||
*/
|
||||
|
||||
struct goldfish_timer_lowerhalf_s
|
||||
{
|
||||
struct oneshot_lowerhalf_s lh; /* Lower half operations */
|
||||
oneshot_callback_t callback; /* Current user interrupt callback */
|
||||
FAR void *arg; /* Argument passed to upper half callback */
|
||||
uintptr_t base; /* Base address of registers */
|
||||
spinlock_t lock; /* Lock for interrupt handling */
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
static int goldfish_timer_maxdelay(FAR struct oneshot_lowerhalf_s *lower,
|
||||
FAR struct timespec *ts);
|
||||
static int goldfish_timer_start(FAR struct oneshot_lowerhalf_s *lower,
|
||||
FAR oneshot_callback_t callback,
|
||||
FAR void *arg,
|
||||
FAR const struct timespec *ts);
|
||||
static int goldfish_timer_cancel(FAR struct oneshot_lowerhalf_s *lower,
|
||||
FAR struct timespec *ts);
|
||||
static int goldfish_timer_current(FAR struct oneshot_lowerhalf_s *lower,
|
||||
FAR struct timespec *ts);
|
||||
|
||||
/****************************************************************************
|
||||
* Private Data
|
||||
****************************************************************************/
|
||||
|
||||
static const struct oneshot_operations_s g_goldfish_timer_ops =
|
||||
{
|
||||
.max_delay = goldfish_timer_maxdelay,
|
||||
.start = goldfish_timer_start,
|
||||
.cancel = goldfish_timer_cancel,
|
||||
.current = goldfish_timer_current,
|
||||
};
|
||||
|
||||
/****************************************************************************
|
||||
* Private Functions
|
||||
****************************************************************************/
|
||||
|
||||
static int goldfish_timer_maxdelay(FAR struct oneshot_lowerhalf_s *lower_,
|
||||
FAR struct timespec *ts)
|
||||
{
|
||||
ts->tv_sec = UINT32_MAX;
|
||||
ts->tv_nsec = UINT32_MAX;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int goldfish_timer_start(FAR struct oneshot_lowerhalf_s *lower_,
|
||||
FAR oneshot_callback_t callback,
|
||||
FAR void *arg,
|
||||
FAR const struct timespec *ts)
|
||||
{
|
||||
FAR struct goldfish_timer_lowerhalf_s *lower =
|
||||
(FAR struct goldfish_timer_lowerhalf_s *)lower_;
|
||||
irqstate_t flags;
|
||||
uint64_t nsec;
|
||||
uint32_t l32;
|
||||
uint32_t h32;
|
||||
|
||||
DEBUGASSERT(lower != NULL);
|
||||
|
||||
flags = spin_lock_irqsave(&lower->lock);
|
||||
|
||||
lower->callback = callback;
|
||||
lower->arg = arg;
|
||||
|
||||
nsec = ts->tv_sec * 1000000000 + ts->tv_nsec;
|
||||
l32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_LOW);
|
||||
h32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_HIGH);
|
||||
nsec += ((uint64_t)h32 << 32) | l32;
|
||||
|
||||
putreg32(1, lower->base + GOLDFISH_TIMER_IRQ_ENABLED);
|
||||
putreg32(nsec >> 32, lower->base + GOLDFISH_TIMER_ALARM_HIGH);
|
||||
putreg32(nsec, lower->base + GOLDFISH_TIMER_ALARM_LOW);
|
||||
|
||||
spin_unlock_irqrestore(&lower->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int goldfish_timer_cancel(FAR struct oneshot_lowerhalf_s *lower_,
|
||||
FAR struct timespec *ts)
|
||||
{
|
||||
struct goldfish_timer_lowerhalf_s *lower =
|
||||
(struct goldfish_timer_lowerhalf_s *)lower_;
|
||||
irqstate_t flags;
|
||||
|
||||
DEBUGASSERT(lower != NULL);
|
||||
|
||||
flags = spin_lock_irqsave(&lower->lock);
|
||||
|
||||
lower->callback = NULL;
|
||||
lower->arg = NULL;
|
||||
|
||||
putreg32(0, lower->base + GOLDFISH_TIMER_IRQ_ENABLED);
|
||||
putreg32(1, lower->base + GOLDFISH_TIMER_CLEAR_ALARM);
|
||||
|
||||
spin_unlock_irqrestore(&lower->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int goldfish_timer_current(FAR struct oneshot_lowerhalf_s *lower_,
|
||||
FAR struct timespec *ts)
|
||||
{
|
||||
FAR struct goldfish_timer_lowerhalf_s *lower =
|
||||
(FAR struct goldfish_timer_lowerhalf_s *)lower_;
|
||||
irqstate_t flags;
|
||||
uint32_t l32;
|
||||
uint32_t h32;
|
||||
uint64_t nsec;
|
||||
|
||||
DEBUGASSERT(lower != NULL);
|
||||
|
||||
flags = spin_lock_irqsave(&lower->lock);
|
||||
|
||||
l32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_LOW);
|
||||
h32 = getreg32(lower->base + GOLDFISH_TIMER_TIME_HIGH);
|
||||
nsec = ((uint64_t)h32 << 32) | l32;
|
||||
|
||||
ts->tv_sec = nsec / NSEC_PER_SEC;
|
||||
ts->tv_nsec = nsec % NSEC_PER_SEC;
|
||||
|
||||
spin_unlock_irqrestore(&lower->lock, flags);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int goldfish_timer_interrupt(int irq,
|
||||
FAR void *context,
|
||||
FAR void *arg)
|
||||
{
|
||||
FAR struct goldfish_timer_lowerhalf_s *lower = arg;
|
||||
oneshot_callback_t callback = NULL;
|
||||
irqstate_t flags;
|
||||
void *cbarg;
|
||||
|
||||
DEBUGASSERT(lower != NULL);
|
||||
|
||||
flags = spin_lock_irqsave(&lower->lock);
|
||||
|
||||
putreg32(1, lower->base + GOLDFISH_TIMER_CLEAR_ALARM);
|
||||
|
||||
if (lower->callback != NULL)
|
||||
{
|
||||
callback = lower->callback;
|
||||
cbarg = lower->arg;
|
||||
lower->callback = NULL;
|
||||
lower->arg = NULL;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&lower->lock, flags);
|
||||
|
||||
/* Then perform the callback */
|
||||
|
||||
if (callback)
|
||||
{
|
||||
callback(&lower->lh, cbarg);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Public Functions
|
||||
****************************************************************************/
|
||||
|
||||
FAR struct oneshot_lowerhalf_s *
|
||||
goldfish_timer_initialize(uintptr_t base, int irq)
|
||||
{
|
||||
FAR struct goldfish_timer_lowerhalf_s *lower;
|
||||
|
||||
lower = kmm_zalloc(sizeof(*lower));
|
||||
if (lower == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lower->lh.ops = &g_goldfish_timer_ops;
|
||||
lower->base = base;
|
||||
|
||||
spin_lock_init(&lower->lock);
|
||||
|
||||
/* Enable timer, but disable interrupt */
|
||||
|
||||
irq_attach(irq, goldfish_timer_interrupt, lower);
|
||||
up_enable_irq(irq);
|
||||
putreg32(0, base + GOLDFISH_TIMER_IRQ_ENABLED);
|
||||
|
||||
return (struct oneshot_lowerhalf_s *)lower;
|
||||
}
|
39
include/nuttx/timers/goldfish_timer.h
Normal file
39
include/nuttx/timers/goldfish_timer.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/****************************************************************************
|
||||
* include/nuttx/timers/goldfish_timer.h
|
||||
*
|
||||
* 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 __INCLUDE_NUTTX_TIMER_GOLDFISH_TIMER_H
|
||||
#define __INCLUDE_NUTTX_TIMER_GOLDFISH_TIMER_H
|
||||
|
||||
/****************************************************************************
|
||||
* Public Function Prototypes
|
||||
****************************************************************************/
|
||||
|
||||
/****************************************************************************
|
||||
* Name: goldfish_timer_initialize
|
||||
*
|
||||
* Description:
|
||||
* Initialize goldfish timer device.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
FAR struct oneshot_lowerhalf_s *
|
||||
goldfish_timer_initialize(uintptr_t base, int irq);
|
||||
|
||||
#endif //__INCLUDE_NUTTX_TIMER_PL031_H
|
Loading…
Reference in a new issue