731d108dd0
The "Arm Ltd. Dual-Timer Module (SP804)" is a simple 32-bit count-down timer IP with interrupt functionality, and is used in some SoCs from various vendors. Add a simple DM compliant timer driver, to allow users of the SP804 to switch to DM_TIMER. This relies on the input clock to be accessible via the DM clock framework, which should be fine as we probably look at fixed-clock's here anyway. We re-program the control register in the probe() function, but keep the divider in place, in case this has been set to something on purpose before. The TRM for the timer IP can be found here: https://developer.arm.com/documentation/ddi0271/latest Signed-off-by: Andre Przywara <andre.przywara@arm.com>
109 lines
2.6 KiB
C
109 lines
2.6 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* ARM PrimeCell Dual-Timer Module (SP804) driver
|
|
* Copyright (C) 2022 Arm Ltd.
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <clk.h>
|
|
#include <dm.h>
|
|
#include <init.h>
|
|
#include <log.h>
|
|
#include <asm/global_data.h>
|
|
#include <dm/ofnode.h>
|
|
#include <mapmem.h>
|
|
#include <dt-structs.h>
|
|
#include <timer.h>
|
|
#include <asm/io.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
#define SP804_TIMERX_LOAD 0x00
|
|
#define SP804_TIMERX_VALUE 0x04
|
|
#define SP804_TIMERX_CONTROL 0x08
|
|
|
|
#define SP804_CTRL_TIMER_ENABLE (1U << 7)
|
|
#define SP804_CTRL_TIMER_PERIODIC (1U << 6)
|
|
#define SP804_CTRL_INT_ENABLE (1U << 5)
|
|
#define SP804_CTRL_TIMER_PRESCALE_SHIFT 2
|
|
#define SP804_CTRL_TIMER_PRESCALE_MASK (3U << SP804_CTRL_TIMER_PRESCALE_SHIFT)
|
|
#define SP804_CTRL_TIMER_32BIT (1U << 1)
|
|
#define SP804_CTRL_ONESHOT (1U << 0)
|
|
|
|
|
|
struct sp804_timer_plat {
|
|
uintptr_t base;
|
|
};
|
|
|
|
static u64 sp804_timer_get_count(struct udevice *dev)
|
|
{
|
|
struct sp804_timer_plat *plat = dev_get_plat(dev);
|
|
uint32_t cntr = readl(plat->base + SP804_TIMERX_VALUE);
|
|
|
|
/* timers are down-counting */
|
|
return ~0u - cntr;
|
|
}
|
|
|
|
static int sp804_clk_of_to_plat(struct udevice *dev)
|
|
{
|
|
struct sp804_timer_plat *plat = dev_get_plat(dev);
|
|
|
|
plat->base = dev_read_addr(dev);
|
|
if (!plat->base)
|
|
return -ENOENT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sp804_timer_probe(struct udevice *dev)
|
|
{
|
|
struct sp804_timer_plat *plat = dev_get_plat(dev);
|
|
struct timer_dev_priv *uc_priv = dev_get_uclass_priv(dev);
|
|
struct clk base_clk;
|
|
unsigned int divider = 1;
|
|
uint32_t ctlr;
|
|
int ret;
|
|
|
|
ctlr = readl(plat->base + SP804_TIMERX_CONTROL);
|
|
ctlr &= SP804_CTRL_TIMER_PRESCALE_MASK;
|
|
switch (ctlr >> SP804_CTRL_TIMER_PRESCALE_SHIFT) {
|
|
case 0x0: divider = 1; break;
|
|
case 0x1: divider = 16; break;
|
|
case 0x2: divider = 256; break;
|
|
case 0x3: printf("illegal!\n"); break;
|
|
}
|
|
|
|
ret = clk_get_by_index(dev, 0, &base_clk);
|
|
if (ret) {
|
|
printf("could not find SP804 timer base clock in DT\n");
|
|
return ret;
|
|
}
|
|
|
|
uc_priv->clock_rate = clk_get_rate(&base_clk) / divider;
|
|
|
|
/* keep divider, free-running, wrapping, no IRQs, 32-bit mode */
|
|
ctlr |= SP804_CTRL_TIMER_32BIT | SP804_CTRL_TIMER_ENABLE;
|
|
writel(ctlr, plat->base + SP804_TIMERX_CONTROL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct timer_ops sp804_timer_ops = {
|
|
.get_count = sp804_timer_get_count,
|
|
};
|
|
|
|
static const struct udevice_id sp804_timer_ids[] = {
|
|
{ .compatible = "arm,sp804" },
|
|
{}
|
|
};
|
|
|
|
U_BOOT_DRIVER(arm_sp804_timer) = {
|
|
.name = "arm_sp804_timer",
|
|
.id = UCLASS_TIMER,
|
|
.of_match = sp804_timer_ids,
|
|
.probe = sp804_timer_probe,
|
|
.ops = &sp804_timer_ops,
|
|
.plat_auto = sizeof(struct sp804_timer_plat),
|
|
.of_to_plat = sp804_clk_of_to_plat,
|
|
};
|