/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MTK_TIMER_SYSTIMER #define MTK_TIMER_DBG_AEE_DUMP #ifdef MTK_TIMER_DBG_AEE_DUMP #ifdef CONFIG_MTK_RAM_CONSOLE #include #include 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. */ 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)); aee_log("irq affinity (bc): %d\n", mtk_stmr_clkevt.irq_affinity_on); #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 "); #endif /* CONFIG_MTK_TIMER_SYSTIMER */ MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Mediatek Clock Event Timer");