blob: 22f7541cc996261406f682b26388aabc1bba3709 [file] [log] [blame]
/*
* Copyright (C) 2018 MediaTek Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See http://www.gnu.org/licenses/gpl-2.0.html for more details.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/jiffies.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/sched/clock.h>
#include <linux/spinlock.h>
#include <clocksource/arm_arch_timer.h>
#include <mt-plat/sync_write.h>
#ifdef CONFIG_MTK_TIMER_SYSTIMER
#define MTK_TIMER_DBG_AEE_DUMP
#ifdef MTK_TIMER_DBG_AEE_DUMP
#ifdef CONFIG_MTK_RAM_CONSOLE
#include <linux/irqchip/mtk-gic-extend.h>
#include <mt-plat/mtk_ram_console.h>
static char dbg_buf[128];
static unsigned int dbg_setevt_cpu;
static uint64_t t_clkevt_in;
static uint64_t t_clkevt_out;
static uint64_t t_setevt_in;
static uint64_t t_setevt_out;
static uint64_t t_hdl_in;
static uint64_t t_hdl_out;
static uint64_t t_setevt_ticks;
#ifdef CONFIG_MTK_RAM_CONSOLE
static DEFINE_SPINLOCK(systimer_lock);
#endif
#define aee_log(fmt, ...) \
do { \
memset(dbg_buf, 0, sizeof(dbg_buf)); \
snprintf(dbg_buf, sizeof(dbg_buf), fmt, ##__VA_ARGS__); \
aee_sram_fiq_log(dbg_buf); \
} while (0)
#endif
#endif
#define STMR0 (0)
#define NR_STMRS (1)
#define STMR_CLKEVT_ID (STMR0)
/* timer bases */
#define STMR_BASE stmrs.tmr_regs
#define STMR0_BASE (STMR_BASE + 0x0040)
/* registers */
#define STMR_CON (0x0)
#define STMR_VAL (0x4)
/* STMR_CON */
#define STMR_CON_EN (1 << 0)
#define STMR_CON_IRQ_EN (1 << 1)
#define STMR_CON_IRQ_CLR (1 << 4)
/* STMR_VAL */
#define STMR_VAL_MAX (0xFFFFFFFF)
/* apxgpt */
#define APXGPT_IRQEN (0x0)
#define APXGPT_IRQACK (0x8)
#define APXGPT_TMR_START (0x10)
#define APXGPT_TMR_SIZE (0x10)
#define APXGPT_TMR_CNT (6)
#define APXGPT_TMR_MASK (0x3F)
struct mtk_stmrs {
int tmr_irq;
void __iomem *tmr_regs;
};
struct mtk_stmr_device {
unsigned int id;
void (*func)(unsigned long data);
void __iomem *base_addr;
};
static struct mtk_stmrs stmrs;
static struct mtk_stmr_device stmr_devs[NR_STMRS];
static inline void noop(unsigned long data) { }
static void(*stmr_handlers[])(unsigned long) = {
noop,
};
static DEFINE_SPINLOCK(stmr_spinlock);
static struct mtk_stmr_device *mtk_stmr_id_to_dev(unsigned int id)
{
return id < NR_STMRS ? stmr_devs + id : NULL;
}
#define mtk_stmr_lock(flags) \
spin_lock_irqsave(&stmr_spinlock, flags)
#define mtk_stmr_unlock(flags) \
spin_unlock_irqrestore(&stmr_spinlock, flags)
static void mtk_stmr_ack_irq(struct mtk_stmr_device *dev);
static irqreturn_t mtk_stmr_handler(int irq, void *dev_id);
static int mtk_stmr_clkevt_next_event(unsigned long cycles,
struct clock_event_device *evt);
static int mtk_stmr_clkevt_shutdown(struct clock_event_device *clk);
static int mtk_stmr_clkevt_oneshot(struct clock_event_device *clk);
static int mtk_stmr_clkevt_resume(struct clock_event_device *clk);
static struct clock_event_device mtk_stmr_clkevt = {
.name = "mtk-clkevt",
/*
* CLOCK_EVT_FEAT_DYNIRQ: Core shall set the interrupt affinity
* dynamically in broadcast mode.
* CLOCK_EVT_FEAT_ONESHOT: Use one-shot mode for tick broadcast.
*/
.features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_DYNIRQ,
.shift = 32,
.rating = 300,
.set_next_event = mtk_stmr_clkevt_next_event,
.set_state_shutdown = mtk_stmr_clkevt_shutdown,
.set_state_oneshot = mtk_stmr_clkevt_oneshot,
.tick_resume = mtk_stmr_clkevt_resume,
};
static struct irqaction mtk_stmr_irq = {
.name = "mtk-clkevt",
.flags = IRQF_TIMER | IRQF_IRQPOLL | IRQF_TRIGGER_HIGH | IRQF_PERCPU,
.handler = mtk_stmr_handler,
.dev_id = &mtk_stmr_clkevt,
};
void mtk_timer_clkevt_aee_dump(void)
{
#if (defined MTK_TIMER_DBG_AEE_DUMP && defined CONFIG_MTK_RAM_CONSOLE)
/*
* Notice: print function cannot be used during AEE
* flow to avoid lock issues.
*/
int cpu_bound;
struct mtk_stmr_device *dev =
mtk_stmr_id_to_dev(STMR_CLKEVT_ID);
/* interrupt, clkevt handler and set next time */
aee_log("[timer-mtk]\n");
aee_log("int handler entry: %llu\n",
t_hdl_in);
aee_log("clkevt handler entry: %llu\n",
t_clkevt_in);
aee_log("set next event entry: %llu\n",
t_setevt_in);
aee_log("set next event ticks: %llu\n",
t_setevt_ticks);
aee_log("set next event exit: %llu\n",
t_setevt_out);
aee_log("clkevt handler exit: %llu\n",
t_clkevt_out);
aee_log("int handler exit: %llu\n",
t_hdl_out);
aee_log("set next event cpu: %u\n",
dbg_setevt_cpu);
aee_log("CON: 0x%x\n",
__raw_readl(dev->base_addr + STMR_CON));
aee_log("VAL: 0x%x\n",
__raw_readl(dev->base_addr + STMR_VAL));
cpu_bound = mt_irq_dump_cpu(mtk_stmr_clkevt.irq);
aee_log("irq affinity (bc, gic): %d, %d\n",
mtk_stmr_clkevt.irq_affinity_on, cpu_bound);
#endif
}
/*
* mtk_stmr_handler users are listed as below,
*
* STMR0: SoC timer for tick-broadcasting (oneshot)
*/
static irqreturn_t mtk_stmr_handler(int irq, void *dev_id)
{
unsigned int id = STMR_CLKEVT_ID;
struct mtk_stmr_device *dev;
#if (defined MTK_TIMER_DBG_AEE_DUMP && defined CONFIG_MTK_RAM_CONSOLE)
t_hdl_in = sched_clock();
#endif
dev = mtk_stmr_id_to_dev(id);
if (likely(dev)) {
#ifdef CONFIG_MTK_RAM_CONSOLE
spin_lock(&systimer_lock);
#endif
mtk_stmr_ack_irq(dev);
#ifdef CONFIG_MTK_RAM_CONSOLE
spin_unlock(&systimer_lock);
#endif
if (likely(stmr_handlers[id]))
stmr_handlers[id]((unsigned long)dev_id);
else
pr_info("timer_mtk: no handler for irq %d\n", irq);
} else
pr_info("timer_mtk: invalid interrupt %d\n", irq);
#if (defined MTK_TIMER_DBG_AEE_DUMP && defined CONFIG_MTK_RAM_CONSOLE)
t_hdl_out = sched_clock();
#endif
return IRQ_HANDLED;
}
static void mtk_stmr_reset(struct mtk_stmr_device *dev)
{
/* Clear IRQ */
mt_reg_sync_writel(STMR_CON_IRQ_CLR | STMR_CON_EN,
dev->base_addr + STMR_CON);
}
static void mtk_stmr_ack_irq(struct mtk_stmr_device *dev)
{
mtk_stmr_reset(dev);
}
static void mtk_stmr_set_handler(
struct mtk_stmr_device *dev, void (*func)(unsigned long))
{
if (func)
stmr_handlers[dev->id] = func;
dev->func = func;
}
static void mtk_stmr_dev_init(void)
{
int i;
for (i = 0; i < NR_STMRS; i++) {
stmr_devs[i].id = i;
stmr_devs[i].base_addr = STMR0_BASE + 0x08 * i;
pr_info("stmr%d, base=0x%lx\n",
i, (unsigned long)stmr_devs[i].base_addr);
}
}
static void mtk_stmr_dev_setup(struct mtk_stmr_device *dev,
void (*func)(unsigned long))
{
if (func)
mtk_stmr_set_handler(dev, func);
}
static int mtk_stmr_clkevt_next_event(unsigned long ticks,
struct clock_event_device *evt)
{
struct mtk_stmr_device *dev = mtk_stmr_id_to_dev(STMR_CLKEVT_ID);
#if (defined MTK_TIMER_DBG_AEE_DUMP && defined CONFIG_MTK_RAM_CONSOLE)
t_setevt_in = sched_clock();
t_setevt_ticks = ticks;
#endif
/*
* stmr spinlock is not required here since spinlock
* "tick_broadcast_lock" shall be held before.
*/
/*
* reset timer first because we do not expect interrupt is triggered
* by old compare value.
*/
#ifdef CONFIG_MTK_RAM_CONSOLE
spin_lock(&systimer_lock);
#endif
mtk_stmr_reset(dev);
mt_reg_sync_writel(STMR_CON_EN, dev->base_addr + STMR_CON);
mt_reg_sync_writel(ticks, dev->base_addr + STMR_VAL);
mt_reg_sync_writel(STMR_CON_EN | STMR_CON_IRQ_EN,
dev->base_addr + STMR_CON);
#ifdef CONFIG_MTK_RAM_CONSOLE
spin_unlock(&systimer_lock);
#endif
#if (defined MTK_TIMER_DBG_AEE_DUMP && defined CONFIG_MTK_RAM_CONSOLE)
t_setevt_out = sched_clock();
dbg_setevt_cpu = smp_processor_id();
#endif
return 0;
}
static int mtk_stmr_clkevt_shutdown(struct clock_event_device *clk)
{
struct mtk_stmr_device *dev = mtk_stmr_id_to_dev(STMR_CLKEVT_ID);
mtk_stmr_reset(dev);
return 0;
}
static int mtk_stmr_clkevt_resume(struct clock_event_device *clk)
{
return mtk_stmr_clkevt_shutdown(clk);
}
static int mtk_stmr_clkevt_oneshot(struct clock_event_device *clk)
{
return 0;
}
static void mtk_stmr_clkevt_handler(unsigned long data)
{
struct clock_event_device *evt = (struct clock_event_device *)data;
#if (defined MTK_TIMER_DBG_AEE_DUMP && defined CONFIG_MTK_RAM_CONSOLE)
t_clkevt_in = sched_clock();
#endif
evt->event_handler(evt);
#if (defined MTK_TIMER_DBG_AEE_DUMP && defined CONFIG_MTK_RAM_CONSOLE)
t_clkevt_out = sched_clock();
#endif
}
static inline void mtk_stmr_setup_clkevt(u32 freq, int irq)
{
struct clock_event_device *evt = &mtk_stmr_clkevt;
struct mtk_stmr_device *dev = mtk_stmr_id_to_dev(STMR_CLKEVT_ID);
/* ensure to provide irq number for tick_broadcast_set_affinity() */
evt->irq = irq;
evt->mult = div_sc(freq, NSEC_PER_SEC, evt->shift);
evt->max_delta_ns = clockevent_delta2ns(0xffffffff, evt);
evt->min_delta_ns = clockevent_delta2ns(1300, evt);
evt->cpumask = cpu_possible_mask;
mtk_stmr_dev_setup(dev, mtk_stmr_clkevt_handler);
pr_info("stmr%d, mult=%u, shift=%u, hz=%d, freq=%d\n",
STMR_CLKEVT_ID, evt->mult, evt->shift, HZ, freq);
clockevents_register_device(evt);
}
static void __init mtk_stmr_init_clkevt(struct device_node *node)
{
u32 freq;
struct clk *clk_evt;
clk_evt = of_clk_get(node, 0);
if (IS_ERR(clk_evt)) {
pr_info("can't get timer clk_evt\n");
return;
}
if (clk_prepare_enable(clk_evt)) {
pr_info("can't prepare clk_evt\n");
clk_put(clk_evt);
return;
}
freq = (u32)clk_get_rate(clk_evt);
WARN(!freq, "can't get freq of clk_evt\n");
mtk_stmr_setup_clkevt(freq, stmrs.tmr_irq);
pr_info("clkevt, freq=%d\n", freq);
}
static int __init mtk_stmr_init(struct device_node *node)
{
int i;
unsigned long save_flags;
mtk_stmr_lock(save_flags);
/* Setup IRQ numbers */
stmrs.tmr_irq = irq_of_parse_and_map(node, 0);
/* Setup IO addresses */
stmrs.tmr_regs = of_iomap(node, 0);
pr_info("base=0x%lx, irq=%d\n",
(unsigned long)stmrs.tmr_regs, stmrs.tmr_irq);
/* setup gpt itself */
mtk_stmr_dev_init();
for (i = 0; i < NR_STMRS; i++)
mtk_stmr_reset(&stmr_devs[i]);
setup_irq(stmrs.tmr_irq, &mtk_stmr_irq);
mtk_stmr_init_clkevt(node);
mtk_stmr_unlock(save_flags);
return 0;
}
CLOCKSOURCE_OF_DECLARE(mtk_timer_systimer, "mediatek,sys_timer", mtk_stmr_init);
MODULE_AUTHOR("Stanley Chu <stanley.chu@mediatek.com>");
#endif /* CONFIG_MTK_TIMER_SYSTIMER */
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Mediatek Clock Event Timer");