476 lines
12 KiB
C
476 lines
12 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/* Copyright(c) 2020 - 2023 Allwinner Technology Co.,Ltd. All rights reserved. */
|
|
/*
|
|
* Allwinner A1X SoCs timer handling.
|
|
*
|
|
* Copyright (C) 2012 Maxime Ripard
|
|
*
|
|
* Maxime Ripard <maxime.ripard@free-electrons.com>
|
|
*
|
|
* Based on code from
|
|
* Allwinner Technology Co., Ltd. <www.allwinnertech.com>
|
|
* Benn Huang <benn@allwinnertech.com>
|
|
*
|
|
* This file is licensed under the terms of the GNU General Public
|
|
* License version 2. This program is licensed "as is" without any
|
|
* warranty of any kind, whether express or implied.
|
|
*/
|
|
|
|
#include <sunxi-log.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/clockchips.h>
|
|
#include <linux/module.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/irq.h>
|
|
#include <linux/irqreturn.h>
|
|
#include <linux/sched_clock.h>
|
|
#include <linux/of.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
|
|
#include "timer-of.h"
|
|
|
|
#define SUN50I_TIMER_MODULE_VERSION "1.1.3"
|
|
|
|
#define TIMER_IRQ_REG 0x00
|
|
#define TIMER_IRQ_EN(val) BIT(val)
|
|
#define TIMER_STA_REG 0x04
|
|
#define TIMER_IRQ_STA(val) BIT(val)
|
|
#define TIMER_CTL_REG(val) (0x20 * (val) + 0x20 + 0x00)
|
|
#define TIMER_EN BIT(0)
|
|
#define TIMER_RELOAD BIT(1)
|
|
#define TIMER_ONESHOT BIT(7)
|
|
#define TIMER_IVL_REG(val) (0x20 * (val) + 0x20 + 0x04)
|
|
#define TIMER_CVL_REG(val) (0x20 * (val) + 0x20 + 0x08)
|
|
#define TIMER_IVH_REG(val) (0x20 * (val) + 0x20 + 0x0c)
|
|
#define TIMER_CVH_REG(val) (0x20 * (val) + 0x20 + 0x10)
|
|
#define TIMER_VL_MASK 0xffffffff
|
|
#define TIMER_VH_MASK 0xffffff
|
|
#define TIMER_VH_OFFSET 32
|
|
|
|
#define TIMER_SYNC_TICKS 3
|
|
|
|
struct sun50i_timer {
|
|
struct timer_of *to;
|
|
struct clk *parent_clk;
|
|
struct clk *bus_clk;
|
|
struct clk *timer0_clk;
|
|
struct clk *timer1_clk;
|
|
struct reset_control *reset;
|
|
};
|
|
/* Registers which needs to be saved and restored before and after sleeping */
|
|
static u32 regs_offset[] = {
|
|
TIMER_IRQ_REG,
|
|
TIMER_STA_REG,
|
|
TIMER_CTL_REG(0),
|
|
TIMER_IVL_REG(0),
|
|
TIMER_CVL_REG(0),
|
|
TIMER_IVH_REG(0),
|
|
TIMER_CVH_REG(0),
|
|
TIMER_CTL_REG(1),
|
|
TIMER_IVL_REG(1),
|
|
TIMER_CVL_REG(1),
|
|
TIMER_IVH_REG(1),
|
|
TIMER_CVH_REG(1),
|
|
};
|
|
static u32 regs_backup[ARRAY_SIZE(regs_offset)];
|
|
|
|
static u64 notrace sun50i_timer_sched_read(void);
|
|
|
|
static void timer_sun50i_clk_deinit(struct sun50i_timer *chip)
|
|
{
|
|
/*
|
|
* We can't reset the timer,
|
|
* because it will cause the timer to be secure mode
|
|
*/
|
|
/* reset_control_assert(chip->reset);*/
|
|
clk_disable_unprepare(chip->bus_clk);
|
|
clk_disable_unprepare(chip->timer0_clk);
|
|
clk_disable_unprepare(chip->timer1_clk);
|
|
}
|
|
|
|
static int timer_sun50i_clk_init(struct sun50i_timer *chip)
|
|
{
|
|
int ret;
|
|
|
|
ret = reset_control_deassert(chip->reset);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(chip->bus_clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(chip->timer0_clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_prepare_enable(chip->timer1_clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = clk_set_parent(chip->timer0_clk, chip->parent_clk);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* When we disable a timer, we need to wait at least for 2 cycles of
|
|
* the timer source clock. We will use for that the clocksource timer
|
|
* that is already setup and runs at the same frequency than the other
|
|
* timers, and we never will be disabled.
|
|
*/
|
|
static void sun50i_clkevt_sync(void)
|
|
{
|
|
u64 old, new;
|
|
|
|
old = sun50i_timer_sched_read();
|
|
|
|
do {
|
|
cpu_relax();
|
|
new = sun50i_timer_sched_read();
|
|
} while (new - old < TIMER_SYNC_TICKS);
|
|
}
|
|
|
|
static void sun50i_clkevt_time_stop(void __iomem *base, u8 timer)
|
|
{
|
|
u32 val = readl(base + TIMER_CTL_REG(timer));
|
|
writel(val & ~TIMER_EN, base + TIMER_CTL_REG(timer));
|
|
sun50i_clkevt_sync();
|
|
}
|
|
|
|
static void sun50i_clkevt_time_setup(void __iomem *base, u8 timer,
|
|
unsigned long delay)
|
|
{
|
|
u32 value;
|
|
|
|
value = delay & TIMER_VL_MASK;
|
|
writel(value, base + TIMER_IVL_REG(timer));
|
|
value = ((delay >> TIMER_VH_OFFSET) & (TIMER_VH_MASK));
|
|
writel(value, base + TIMER_IVH_REG(timer));
|
|
}
|
|
|
|
static void sun50i_clkevt_time_start(void __iomem *base, u8 timer,
|
|
bool periodic)
|
|
{
|
|
u32 val = readl(base + TIMER_CTL_REG(timer));
|
|
|
|
if (periodic)
|
|
val &= ~TIMER_ONESHOT;
|
|
else
|
|
val |= TIMER_ONESHOT;
|
|
|
|
writel(val | TIMER_EN | TIMER_RELOAD,
|
|
base + TIMER_CTL_REG(timer));
|
|
}
|
|
|
|
static inline void save_regs(void __iomem *base)
|
|
{
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(regs_offset); i++)
|
|
regs_backup[i] = readl(base + regs_offset[i]);
|
|
}
|
|
|
|
static inline void restore_regs(void __iomem *base)
|
|
{
|
|
int i;
|
|
for (i = 0; i < ARRAY_SIZE(regs_offset); i++)
|
|
writel(regs_backup[i], base + regs_offset[i]);
|
|
}
|
|
|
|
static int sun50i_clkevt_shutdown(struct clock_event_device *evt)
|
|
{
|
|
struct timer_of *to = to_timer_of(evt);
|
|
|
|
save_regs(timer_of_base(to));
|
|
sun50i_clkevt_time_stop(timer_of_base(to), 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sun50i_timer_resume(struct clock_event_device *evt)
|
|
{
|
|
struct sun50i_timer *chip;
|
|
struct timer_of *to = to_timer_of(evt);
|
|
|
|
chip = to->private_data;
|
|
|
|
timer_sun50i_clk_init(chip);
|
|
restore_regs(timer_of_base(to));
|
|
sun50i_clkevt_time_stop(timer_of_base(to), 0);
|
|
}
|
|
|
|
static void sun50i_timer_suspend(struct clock_event_device *evt)
|
|
{
|
|
struct sun50i_timer *chip;
|
|
struct timer_of *to = to_timer_of(evt);
|
|
|
|
chip = to->private_data;
|
|
save_regs(timer_of_base(to));
|
|
sun50i_clkevt_time_stop(timer_of_base(to), 0);
|
|
timer_sun50i_clk_deinit(chip);
|
|
}
|
|
|
|
static int sun50i_clkevt_set_oneshot(struct clock_event_device *evt)
|
|
{
|
|
struct timer_of *to = to_timer_of(evt);
|
|
|
|
sun50i_clkevt_time_stop(timer_of_base(to), 0);
|
|
sun50i_clkevt_time_start(timer_of_base(to), 0, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sun50i_clkevt_set_periodic(struct clock_event_device *evt)
|
|
{
|
|
struct timer_of *to = to_timer_of(evt);
|
|
|
|
sun50i_clkevt_time_stop(timer_of_base(to), 0);
|
|
sun50i_clkevt_time_setup(timer_of_base(to), 0, timer_of_period(to));
|
|
sun50i_clkevt_time_start(timer_of_base(to), 0, true);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sun50i_clkevt_next_event(unsigned long evt,
|
|
struct clock_event_device *clkevt)
|
|
{
|
|
struct timer_of *to = to_timer_of(clkevt);
|
|
|
|
sun50i_clkevt_time_stop(timer_of_base(to), 0);
|
|
sun50i_clkevt_time_setup(timer_of_base(to), 0, evt - TIMER_SYNC_TICKS);
|
|
sun50i_clkevt_time_start(timer_of_base(to), 0, false);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sun50i_timer_clear_interrupt(void __iomem *base)
|
|
{
|
|
writel(TIMER_IRQ_STA(0), base + TIMER_STA_REG);
|
|
}
|
|
|
|
static irqreturn_t sun50i_timer_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct clock_event_device *evt = (struct clock_event_device *)dev_id;
|
|
struct timer_of *to = to_timer_of(evt);
|
|
|
|
sun50i_timer_clear_interrupt(timer_of_base(to));
|
|
evt->event_handler(evt);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct timer_of to = {
|
|
.flags = TIMER_OF_IRQ | TIMER_OF_BASE,
|
|
|
|
.clkevt = {
|
|
.name = "sun50i_tick",
|
|
.rating = 350,
|
|
.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT,
|
|
.set_state_shutdown = sun50i_clkevt_shutdown,
|
|
.set_state_periodic = sun50i_clkevt_set_periodic,
|
|
.set_state_oneshot = sun50i_clkevt_set_oneshot,
|
|
.set_next_event = sun50i_clkevt_next_event,
|
|
.cpumask = cpu_possible_mask,
|
|
.resume = sun50i_timer_resume,
|
|
.suspend = sun50i_timer_suspend,
|
|
},
|
|
|
|
.of_irq = {
|
|
.handler = sun50i_timer_interrupt,
|
|
.flags = IRQF_TIMER | IRQF_IRQPOLL,
|
|
},
|
|
};
|
|
|
|
static u64 notrace sun50i_timer_sched_read(void)
|
|
{
|
|
u64 val_low, val_high;
|
|
|
|
val_high = (~readl(timer_of_base(&to) + TIMER_CVH_REG(1))) & TIMER_VH_MASK;
|
|
|
|
val_low = (~readl(timer_of_base(&to) + TIMER_CVL_REG(1))) & TIMER_VL_MASK;
|
|
|
|
return ((val_high << TIMER_VH_OFFSET) | val_low);
|
|
}
|
|
|
|
static u64 sun50i_timer_readl_down(struct clocksource *c)
|
|
{
|
|
return sun50i_timer_sched_read();
|
|
}
|
|
|
|
static int sun50i_timer_resource_get(struct sun50i_timer *chip, struct device_node *np)
|
|
{
|
|
chip->parent_clk = of_clk_get_by_name(np, "parent");
|
|
if (IS_ERR(chip->parent_clk)) {
|
|
sunxi_err(NULL, "request parent clock failed\n");
|
|
return PTR_ERR(chip->parent_clk);
|
|
}
|
|
|
|
chip->bus_clk = of_clk_get_by_name(np, "bus");
|
|
if (IS_ERR(chip->bus_clk)) {
|
|
sunxi_err(NULL, "request bus clock failed\n");
|
|
return PTR_ERR(chip->bus_clk);
|
|
}
|
|
|
|
chip->timer0_clk = of_clk_get_by_name(np, "timer0-mod");
|
|
if (IS_ERR(chip->timer0_clk)) {
|
|
sunxi_err(NULL, "request timer0 clock failed\n");
|
|
return PTR_ERR(chip->timer0_clk);
|
|
}
|
|
|
|
chip->timer1_clk = of_clk_get_by_name(np, "timer1-mod");
|
|
if (IS_ERR(chip->timer1_clk)) {
|
|
sunxi_err(NULL, "request timer1 clock failed\n");
|
|
return PTR_ERR(chip->timer1_clk);
|
|
}
|
|
|
|
chip->reset = of_reset_control_get(np, NULL);
|
|
if (IS_ERR_OR_NULL(chip->reset)) {
|
|
sunxi_err(NULL, "request reset failed\n");
|
|
return PTR_ERR(chip->reset);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sunxi_timer_init(struct device_node *node)
|
|
{
|
|
int ret;
|
|
u32 val;
|
|
struct sun50i_timer *chip;
|
|
struct of_timer_clk *of_clk;
|
|
|
|
chip = kzalloc(sizeof(*chip), GFP_KERNEL);
|
|
if (IS_ERR_OR_NULL(chip))
|
|
return -ENOMEM;
|
|
|
|
chip->to = &to;
|
|
to.private_data = chip;
|
|
|
|
ret = timer_of_init(node, &to);
|
|
if (ret) {
|
|
sunxi_err(NULL, "sun50i timer of init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = sun50i_timer_resource_get(chip, node);
|
|
if (ret) {
|
|
sunxi_err(NULL, "sun50i timer of resource get failed\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = timer_sun50i_clk_init(chip);
|
|
if (ret) {
|
|
sunxi_err(NULL, "sun50i timer of clk init failed\n");
|
|
return ret;
|
|
}
|
|
|
|
of_clk = &(to.of_clk);
|
|
of_clk->clk = chip->timer0_clk;
|
|
of_clk->rate = clk_get_rate(of_clk->clk);
|
|
if (!of_clk->rate) {
|
|
sunxi_err(NULL, "Failed to get clock rate\n");
|
|
return ret;
|
|
}
|
|
of_clk->period = DIV_ROUND_UP(of_clk->rate, HZ);
|
|
|
|
sun50i_clkevt_time_setup(timer_of_base(&to), 1, ~0);
|
|
sun50i_clkevt_time_start(timer_of_base(&to), 1, true);
|
|
|
|
sunxi_info(NULL, "sun50i timer init:0x%llx, driver version: %s\n", sun50i_timer_sched_read(), SUN50I_TIMER_MODULE_VERSION);
|
|
|
|
sched_clock_register(sun50i_timer_sched_read, 56, timer_of_rate(&to));
|
|
|
|
ret = clocksource_mmio_init(timer_of_base(&to) + TIMER_CVL_REG(1),
|
|
node->name, timer_of_rate(&to), 350, 56,
|
|
sun50i_timer_readl_down);
|
|
if (ret) {
|
|
sunxi_err(NULL, "Failed to register clocksource\n");
|
|
return ret;
|
|
}
|
|
|
|
/* Make sure timer is stopped before playing with interrupts */
|
|
sun50i_clkevt_time_stop(timer_of_base(&to), 0);
|
|
|
|
/* clear timer0 interrupt */
|
|
sun50i_timer_clear_interrupt(timer_of_base(&to));
|
|
|
|
clockevents_config_and_register(&to.clkevt, timer_of_rate(&to),
|
|
TIMER_SYNC_TICKS, 0xffffffffffffff);
|
|
|
|
/* Enable timer0 interrupt */
|
|
val = readl(timer_of_base(&to) + TIMER_IRQ_REG);
|
|
writel(val | TIMER_IRQ_EN(0), timer_of_base(&to) + TIMER_IRQ_REG);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_AW_KERNEL_ORIGIN)
|
|
static int __init sun50i_timer_init(struct device_node *node)
|
|
{
|
|
int ret;
|
|
|
|
ret = sunxi_timer_init(node);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
TIMER_OF_DECLARE(sunxi_hstimer, "allwinner,hstimer-v100",
|
|
sun50i_timer_init);
|
|
TIMER_OF_DECLARE(sun50i, "allwinner,sun50i-timer",
|
|
sun50i_timer_init);
|
|
#else
|
|
static int sun50i_timer_probe(struct platform_device *pdev)
|
|
{
|
|
struct device_node *node = pdev->dev.of_node;
|
|
int ret;
|
|
|
|
ret = sunxi_timer_init(node);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id sun55iw3_sunxi_timer_ids[] = {
|
|
{ .compatible = "allwinner,sun50i-timer" },
|
|
{ .compatible = "allwinner,hstimer-v100" },
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver sun55iw3_timer_driver = {
|
|
.probe = sun50i_timer_probe,
|
|
.driver = {
|
|
.name = "sun55iw3-timer",
|
|
.of_match_table = sun55iw3_sunxi_timer_ids,
|
|
},
|
|
};
|
|
|
|
static int __init sunxi_timer_sun55iw3_init(void)
|
|
{
|
|
int ret;
|
|
|
|
ret = platform_driver_register(&sun55iw3_timer_driver);
|
|
if (ret)
|
|
sunxi_err(NULL, "register sun50i-timer sun55iw3 failed!\n");
|
|
|
|
return ret;
|
|
}
|
|
module_init(sunxi_timer_sun55iw3_init);
|
|
|
|
static void __exit sunxi_timer_sun55iw3_exit(void)
|
|
{
|
|
return platform_driver_unregister(&sun55iw3_timer_driver);
|
|
}
|
|
module_exit(sunxi_timer_sun55iw3_exit);
|
|
#endif
|
|
|
|
MODULE_AUTHOR("danghao <danghao@allwinnertech.com>");
|
|
MODULE_VERSION(SUN50I_TIMER_MODULE_VERSION);
|
|
MODULE_LICENSE("GPL");
|