6db4831e98
Android 14
1057 lines
24 KiB
C
1057 lines
24 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) 2019 MediaTek Inc.
|
|
*/
|
|
|
|
#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 <clocksource/arm_arch_timer.h>
|
|
|
|
#include <mt-plat/mtk_gpt.h>
|
|
#include <mt-plat/sync_write.h>
|
|
|
|
#include <linux/irqchip/mtk-gic-extend.h> /* for aee dump */
|
|
#include <linux/sched.h> /* for aee dump */
|
|
|
|
#ifndef cycle_t
|
|
typedef u64 cycle_t;
|
|
#endif
|
|
|
|
#define APXGPT_OF_COMPTIBLE_NAME "mediatek,apxgpt"
|
|
|
|
/*
|
|
* CONFIG_MTK_TIMER_BC_IRQ_FORCE_CPU0:
|
|
*
|
|
* Always force irq affinity to cpu 0. For debugging purpose only.
|
|
*/
|
|
|
|
/* #define CONFIG_MTK_TIMER_BC_IRQ_FORCE_CPU0 */
|
|
|
|
/*
|
|
* CONFIG_MTK_TIMER_DEBUG:
|
|
*
|
|
* Enable debugging mechanism.
|
|
*/
|
|
|
|
/* #define CONFIG_MTK_TIMER_DEBUG */
|
|
|
|
/*
|
|
* CONFIG_MTK_TIMER_AEE_DUMP:
|
|
*
|
|
* Enable dump during debugging flow, for example, HWT.
|
|
* The debugging information will be collected to DB.
|
|
*/
|
|
#define CONFIG_MTK_TIMER_AEE_DUMP
|
|
|
|
#ifdef CONFIG_MTK_TIMER_APXGPT_V1
|
|
#ifdef CONFIG_MTK_TIMER_AEE_DUMP
|
|
#ifdef CONFIG_MTK_RAM_CONSOLE
|
|
#include <mt-plat/mtk_ram_console.h>
|
|
|
|
static char gpt_clkevt_aee_dump_buf[128];
|
|
static uint64_t gpt_time_clkevt_handler_entry;
|
|
static uint64_t gpt_time_clkevt_handler_exit;
|
|
static uint64_t gpt_time_clkevt_set_next_event_entry;
|
|
static uint64_t gpt_time_clkevt_set_next_event_exit;
|
|
static uint64_t gpt_time_int_handler_entry;
|
|
static uint64_t gpt_time_int_handler_exit;
|
|
|
|
#define _MTK_TIMER_DBG_AEE_DUMP
|
|
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
/*
|
|
* Common apxpgt definition and functions used when,
|
|
* 1. CONFIG_MTK_TIMER_APXGPT_V1 on: apxgpt will be used.
|
|
* 2. CONFIG_MTK_TIMER_SYSTIMER on: apxgpt will be un-initialized
|
|
* in init stage.
|
|
*/
|
|
#if defined(CONFIG_MTK_TIMER_APXGPT_V1) || defined(CONFIG_MTK_TIMER_SYSTIMER)
|
|
|
|
#define GPT_CLKEVT_ID (GPT1)
|
|
#define GPT_CLKSRC_ID (GPT2)
|
|
|
|
#define GPT_IRQEN (AP_XGPT_BASE + 0x0000)
|
|
#define GPT_IRQSTA (AP_XGPT_BASE + 0x0004)
|
|
#define GPT_IRQACK (AP_XGPT_BASE + 0x0008)
|
|
#define GPT1_BASE (AP_XGPT_BASE + 0x0010)
|
|
|
|
#define GPT_CON (0x00)
|
|
#define GPT_CLK (0x04)
|
|
#define GPT_CNT (0x08)
|
|
#define GPT_CMP (0x0C)
|
|
#define GPT_CNTH (0x18)
|
|
#define GPT_CMPH (0x1C)
|
|
|
|
#define GPT_CON_ENABLE (0x1 << 0)
|
|
#define GPT_CON_CLRCNT (0x1 << 1)
|
|
#define GPT_CON_OPMODE (0x3 << 4)
|
|
|
|
#define GPT_OPMODE_MASK (0x3)
|
|
#define GPT_CLKDIV_MASK (0xf)
|
|
#define GPT_CLKSRC_MASK (0x1)
|
|
|
|
#define GPT_OPMODE_OFFSET (4)
|
|
#define GPT_CLKSRC_OFFSET (4)
|
|
|
|
#define GPT_FEAT_64_BIT (0x0001)
|
|
#define GPT_ISR (0x0010)
|
|
#define GPT_IN_USE (0x0100)
|
|
|
|
/************define this for 32/64 compatible**************/
|
|
#define GPT_BIT_MASK_L 0x00000000FFFFFFFF
|
|
#define GPT_BIT_MASK_H 0xFFFFFFFF00000000
|
|
/****************************************************/
|
|
|
|
struct mt_gpt_timers {
|
|
int tmr_irq;
|
|
void __iomem *tmr_regs;
|
|
};
|
|
|
|
struct mt_gpt_device {
|
|
unsigned int id;
|
|
unsigned int mode;
|
|
unsigned int clksrc;
|
|
unsigned int clkdiv;
|
|
unsigned int cmp[2];
|
|
void (*func)(unsigned long);
|
|
int flags;
|
|
int features;
|
|
void __iomem *base_addr;
|
|
};
|
|
|
|
/*
|
|
* Return GPT4 count(before init clear) to record
|
|
* kernel start time between LK and kernel
|
|
*/
|
|
|
|
/* 1000000 / 76.92ns = 13000.520 */
|
|
#define GPT4_1MS_TICK ((u32)(13000))
|
|
#define GPT4_BASE (AP_XGPT_BASE + 0x0040)
|
|
|
|
#define mt_gpt_set_reg(val, addr) \
|
|
mt_reg_sync_writel(__raw_readl(addr)|(val), addr)
|
|
#define mt_gpt_clr_reg(val, addr) \
|
|
mt_reg_sync_writel(__raw_readl(addr)&~(val), addr)
|
|
|
|
static struct mt_gpt_timers gpt_timers;
|
|
static struct mt_gpt_device gpt_devs[NR_GPTS];
|
|
|
|
#define AP_XGPT_BASE gpt_timers.tmr_regs
|
|
|
|
static void __gpt_disable_irq(struct mt_gpt_device *dev)
|
|
{
|
|
mt_gpt_clr_reg(0x1 << (dev->id), GPT_IRQEN);
|
|
}
|
|
|
|
static void __gpt_ack_irq(struct mt_gpt_device *dev)
|
|
{
|
|
mt_reg_sync_writel(0x1 << (dev->id), GPT_IRQACK);
|
|
}
|
|
|
|
static void __gpt_reset(struct mt_gpt_device *dev)
|
|
{
|
|
mt_reg_sync_writel(0x0, dev->base_addr + GPT_CON);
|
|
__gpt_disable_irq(dev);
|
|
__gpt_ack_irq(dev);
|
|
mt_reg_sync_writel(0x0, dev->base_addr + GPT_CLK);
|
|
mt_reg_sync_writel(0x2, dev->base_addr + GPT_CON);
|
|
mt_reg_sync_writel(0x0, dev->base_addr + GPT_CMP);
|
|
if (dev->features & GPT_FEAT_64_BIT)
|
|
mt_reg_sync_writel(0, dev->base_addr + GPT_CMPH);
|
|
}
|
|
|
|
static void gpt_devs_init(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NR_GPTS; i++) {
|
|
gpt_devs[i].id = i;
|
|
gpt_devs[i].base_addr = GPT1_BASE + 0x10 * i;
|
|
pr_info("gpt%d, base=0x%lx\n",
|
|
i + 1, (unsigned long)gpt_devs[i].base_addr);
|
|
}
|
|
|
|
gpt_devs[GPT6].features |= GPT_FEAT_64_BIT;
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_MTK_TIMER_APXGPT_V1
|
|
|
|
static DEFINE_SPINLOCK(gpt_lock);
|
|
|
|
static unsigned int boot_time_value;
|
|
|
|
static unsigned int gpt_boot_up_time(void)
|
|
{
|
|
unsigned int tick;
|
|
|
|
tick = __raw_readl(GPT4_BASE + GPT_CNT);
|
|
return ((tick + (GPT4_1MS_TICK - 1)) / GPT4_1MS_TICK);
|
|
}
|
|
/*********************************************************/
|
|
|
|
static struct mt_gpt_device *id_to_dev(unsigned int id)
|
|
{
|
|
return id < NR_GPTS ? gpt_devs + id : NULL;
|
|
}
|
|
|
|
#define gpt_update_lock(flags) spin_lock_irqsave(&gpt_lock, flags)
|
|
|
|
#define gpt_update_unlock(flags) spin_unlock_irqrestore(&gpt_lock, flags)
|
|
|
|
static inline void noop(unsigned long data) { }
|
|
static void(*handlers[])(unsigned long) = {
|
|
noop,
|
|
noop,
|
|
noop,
|
|
noop,
|
|
noop,
|
|
noop,
|
|
noop,
|
|
};
|
|
|
|
static struct tasklet_struct task[NR_GPTS];
|
|
static void task_sched(unsigned long data)
|
|
{
|
|
unsigned int id = (unsigned int)data;
|
|
|
|
tasklet_schedule(&task[id]);
|
|
}
|
|
|
|
static irqreturn_t gpt_handler(int irq, void *dev_id);
|
|
static void __gpt_ack_irq(struct mt_gpt_device *dev);
|
|
static cycle_t mt_gpt_read(struct clocksource *cs);
|
|
static int mt_gpt_clkevt_next_event(unsigned long cycles,
|
|
struct clock_event_device *evt);
|
|
static int mt_gpt_clkevt_shutdown(struct clock_event_device *clk);
|
|
static int mt_gpt_clkevt_oneshot(struct clock_event_device *clk);
|
|
static int mt_gpt_clkevt_resume(struct clock_event_device *clk);
|
|
static int mt_gpt_set_periodic(struct clock_event_device *clk);
|
|
|
|
static struct clocksource gpt_clocksource = {
|
|
.name = "mtk-timer",
|
|
.rating = 300,
|
|
.read = mt_gpt_read,
|
|
.mask = CLOCKSOURCE_MASK(32),
|
|
.shift = 25,
|
|
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
|
|
};
|
|
|
|
static struct clock_event_device gpt_clockevent = {
|
|
.name = "mtk_tick",
|
|
/*
|
|
* 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.
|
|
*/
|
|
#ifdef CONFIG_MTK_TIMER_BC_IRQ_FORCE_CPU0
|
|
.features = CLOCK_EVT_FEAT_ONESHOT,
|
|
#else
|
|
.features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_DYNIRQ,
|
|
#endif
|
|
.shift = 32,
|
|
.rating = 300,
|
|
.set_next_event = mt_gpt_clkevt_next_event,
|
|
.set_state_shutdown = mt_gpt_clkevt_shutdown,
|
|
.set_state_periodic = mt_gpt_set_periodic,
|
|
.set_state_oneshot = mt_gpt_clkevt_oneshot,
|
|
.tick_resume = mt_gpt_clkevt_resume,
|
|
};
|
|
|
|
static struct irqaction gpt_irq = {
|
|
.name = "mt-gpt",
|
|
.flags = IRQF_TIMER | IRQF_IRQPOLL | IRQF_TRIGGER_LOW | IRQF_PERCPU,
|
|
.handler = gpt_handler,
|
|
.dev_id = &gpt_clockevent,
|
|
};
|
|
|
|
static inline unsigned int gpt_get_and_ack_irq(void)
|
|
{
|
|
unsigned int id;
|
|
unsigned int mask;
|
|
unsigned int status = __raw_readl(GPT_IRQSTA);
|
|
|
|
for (id = GPT1; id < NR_GPTS; id++) {
|
|
mask = 0x1 << id;
|
|
if (status & mask) {
|
|
mt_reg_sync_writel(mask, GPT_IRQACK);
|
|
break;
|
|
}
|
|
}
|
|
return id;
|
|
}
|
|
|
|
/*
|
|
* gpt_handler users are listed as below,
|
|
*
|
|
* For ACAO project:
|
|
* GPT1: SoC timer for tick-broadcasting (oneshot)
|
|
*
|
|
* For HPS project:
|
|
* GPT4: Wakeup source for MTK idle framework (oneshot)
|
|
*/
|
|
static irqreturn_t gpt_handler(int irq, void *dev_id)
|
|
{
|
|
unsigned int id;
|
|
struct mt_gpt_device *dev;
|
|
|
|
#ifdef _MTK_TIMER_DBG_AEE_DUMP
|
|
gpt_time_int_handler_entry = sched_clock();
|
|
#endif
|
|
|
|
id = gpt_get_and_ack_irq();
|
|
dev = id_to_dev(id);
|
|
|
|
if (likely(dev)) {
|
|
if (!(dev->flags & GPT_ISR))
|
|
handlers[id](id);
|
|
else
|
|
handlers[id]((unsigned long)dev_id);
|
|
} else
|
|
pr_info("GPT id is %d\n", id);
|
|
|
|
#ifdef _MTK_TIMER_DBG_AEE_DUMP
|
|
gpt_time_int_handler_exit = sched_clock();
|
|
#endif
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void __gpt_enable_irq(struct mt_gpt_device *dev)
|
|
{
|
|
mt_gpt_set_reg(0x1 << (dev->id), GPT_IRQEN);
|
|
}
|
|
|
|
static void __gpt_get_cnt(struct mt_gpt_device *dev, unsigned int *ptr)
|
|
{
|
|
*ptr = __raw_readl(dev->base_addr + GPT_CNT);
|
|
if (dev->features & GPT_FEAT_64_BIT)
|
|
*(++ptr) = __raw_readl(dev->base_addr + GPT_CNTH);
|
|
}
|
|
|
|
static void __gpt_get_cmp(struct mt_gpt_device *dev, unsigned int *ptr)
|
|
{
|
|
*ptr = __raw_readl(dev->base_addr + GPT_CMP);
|
|
if (dev->features & GPT_FEAT_64_BIT)
|
|
*(++ptr) = __raw_readl(dev->base_addr + GPT_CMPH);
|
|
}
|
|
|
|
static void __gpt_set_mode(struct mt_gpt_device *dev, unsigned int mode)
|
|
{
|
|
unsigned int ctl = __raw_readl(dev->base_addr + GPT_CON);
|
|
|
|
mode <<= GPT_OPMODE_OFFSET;
|
|
|
|
ctl &= ~GPT_CON_OPMODE;
|
|
ctl |= mode;
|
|
|
|
mt_reg_sync_writel(ctl, dev->base_addr + GPT_CON);
|
|
|
|
dev->mode = mode;
|
|
}
|
|
|
|
static void __gpt_set_clk(struct mt_gpt_device *dev,
|
|
unsigned int clksrc, unsigned int clkdiv)
|
|
{
|
|
unsigned int clk = (clksrc << GPT_CLKSRC_OFFSET) | clkdiv;
|
|
|
|
mt_reg_sync_writel(clk, dev->base_addr + GPT_CLK);
|
|
|
|
dev->clksrc = clksrc;
|
|
dev->clkdiv = clkdiv;
|
|
}
|
|
|
|
static void __gpt_set_cmp(struct mt_gpt_device *dev, unsigned int cmpl,
|
|
unsigned int cmph)
|
|
{
|
|
mt_reg_sync_writel(cmpl, dev->base_addr + GPT_CMP);
|
|
dev->cmp[0] = cmpl;
|
|
|
|
if (dev->features & GPT_FEAT_64_BIT) {
|
|
mt_reg_sync_writel(cmph, dev->base_addr + GPT_CMPH);
|
|
dev->cmp[1] = cmpl;
|
|
}
|
|
}
|
|
|
|
static void __gpt_clrcnt(struct mt_gpt_device *dev)
|
|
{
|
|
mt_gpt_set_reg(GPT_CON_CLRCNT, dev->base_addr + GPT_CON);
|
|
while (__raw_readl(dev->base_addr + GPT_CNT))
|
|
cpu_relax();
|
|
}
|
|
|
|
static void __gpt_start(struct mt_gpt_device *dev)
|
|
{
|
|
mt_gpt_set_reg(GPT_CON_ENABLE, dev->base_addr + GPT_CON);
|
|
}
|
|
|
|
static void __gpt_wait_clrcnt(void)
|
|
{
|
|
/*
|
|
* if gpt is running in 32K domain, it needs 3T (~90 us) for clearing
|
|
* old counter.
|
|
*/
|
|
#define WAIT_CLR_CNT_TIME_NS 100000
|
|
|
|
uint64_t start_time = 0, end_time = 0;
|
|
|
|
start_time = sched_clock();
|
|
end_time = start_time;
|
|
|
|
while ((end_time - start_time) < WAIT_CLR_CNT_TIME_NS)
|
|
end_time = sched_clock();
|
|
}
|
|
|
|
static void __gpt_wait_clrcnt_then_start(struct mt_gpt_device *dev)
|
|
{
|
|
__gpt_wait_clrcnt();
|
|
__gpt_start(dev);
|
|
}
|
|
|
|
static void __gpt_stop(struct mt_gpt_device *dev)
|
|
{
|
|
mt_gpt_clr_reg(GPT_CON_ENABLE, dev->base_addr + GPT_CON);
|
|
}
|
|
static void __gpt_set_flags(struct mt_gpt_device *dev, unsigned int flags)
|
|
{
|
|
dev->flags |= flags;
|
|
}
|
|
|
|
static void __gpt_set_handler(struct mt_gpt_device *dev,
|
|
void (*func)(unsigned long))
|
|
{
|
|
if (func) {
|
|
if (dev->flags & GPT_ISR)
|
|
handlers[dev->id] = func;
|
|
else {
|
|
tasklet_init(&task[dev->id], func, 0);
|
|
handlers[dev->id] = task_sched;
|
|
}
|
|
}
|
|
dev->func = func;
|
|
}
|
|
|
|
static void setup_gpt_dev_locked(struct mt_gpt_device *dev, unsigned int mode,
|
|
unsigned int clksrc, unsigned int clkdiv, unsigned int cmp,
|
|
void (*func)(unsigned long), unsigned int flags)
|
|
{
|
|
__gpt_set_flags(dev, flags | GPT_IN_USE);
|
|
|
|
__gpt_set_mode(dev, mode & GPT_OPMODE_MASK);
|
|
__gpt_set_clk(dev, clksrc & GPT_CLKSRC_MASK, clkdiv & GPT_CLKDIV_MASK);
|
|
|
|
if (func)
|
|
__gpt_set_handler(dev, func);
|
|
|
|
if (dev->mode != GPT_FREE_RUN) {
|
|
__gpt_set_cmp(dev, cmp, 0);
|
|
if (!(dev->flags & GPT_NOIRQEN))
|
|
__gpt_enable_irq(dev);
|
|
}
|
|
|
|
if (!(dev->flags & GPT_NOAUTOEN))
|
|
__gpt_start(dev);
|
|
}
|
|
|
|
static int mt_gpt_clkevt_next_event(unsigned long cycles,
|
|
struct clock_event_device *evt)
|
|
{
|
|
struct mt_gpt_device *dev = id_to_dev(GPT_CLKEVT_ID);
|
|
|
|
#ifdef _MTK_TIMER_DBG_AEE_DUMP
|
|
gpt_time_clkevt_set_next_event_entry = sched_clock();
|
|
#endif
|
|
|
|
/*
|
|
* disable irq first because we do not expect interrupt is triggered
|
|
* by old compare value.
|
|
*/
|
|
__gpt_disable_irq(dev);
|
|
|
|
/*
|
|
* Configure gpt1 to use 13MHz clock during re-configuration.
|
|
*
|
|
* Reason: Clock synchronization issue may happen if gpt is in 32KHz
|
|
* domain during re-configuration. For example: Updating cmp
|
|
* value may need to wait a period of time (e.g., 3.5T) to let
|
|
* gpt hw finish jobs: gpt hw will clear counter value
|
|
* automatically while setting new cmp value.
|
|
*
|
|
* GPT_EN shall be enabled after gpt hw finishes above job,
|
|
* otherwise gpt may work abnormally, e.g., wrong cmp value
|
|
* is latched or counter value is not reset.
|
|
*
|
|
* If gpt is running under 13MHz, above waiting is not required
|
|
* since gpt hw guarantees that GPT_EN is applied after above
|
|
* job is done.
|
|
*/
|
|
__gpt_set_clk(dev, GPT_CLK_SRC_SYS & GPT_CLKSRC_MASK,
|
|
GPT_CLK_DIV_1 & GPT_CLKDIV_MASK);
|
|
|
|
__gpt_stop(dev);
|
|
|
|
if (cycles < 3) {
|
|
pr_info("[mt_gpt] invalid cycles < 3\n");
|
|
cycles = 3;
|
|
}
|
|
|
|
/*
|
|
* Do cmp first because updating cmp will trigger most
|
|
* complicated behavior in gpt hw. See above description.
|
|
*/
|
|
__gpt_set_cmp(dev, cycles, 0);
|
|
|
|
/* ack irq */
|
|
__gpt_ack_irq(dev);
|
|
|
|
/* ensure irq is enabled before next running */
|
|
__gpt_enable_irq(dev);
|
|
|
|
/*
|
|
* Configure gpt1 to use 32KHz clock before enabling.
|
|
*
|
|
* Reason: 13MHz clock source may be disabled during some
|
|
* low-power scenarios, e.g., SODI. We shall use a
|
|
* always-on clock after enabling, e.g., 32KHz.
|
|
*/
|
|
__gpt_set_clk(dev, GPT_CLK_SRC_RTC & GPT_CLKSRC_MASK,
|
|
GPT_CLK_DIV_1 & GPT_CLKDIV_MASK);
|
|
|
|
__gpt_start(dev);
|
|
|
|
#ifdef _MTK_TIMER_DBG_AEE_DUMP
|
|
gpt_time_clkevt_set_next_event_exit = sched_clock();
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt_gpt_clkevt_shutdown(struct clock_event_device *clk)
|
|
{
|
|
struct mt_gpt_device *dev = id_to_dev(GPT_CLKEVT_ID);
|
|
|
|
__gpt_stop(dev);
|
|
__gpt_disable_irq(dev);
|
|
__gpt_ack_irq(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt_gpt_clkevt_resume(struct clock_event_device *clk)
|
|
{
|
|
return mt_gpt_clkevt_shutdown(clk);
|
|
}
|
|
|
|
static int mt_gpt_clkevt_oneshot(struct clock_event_device *clk)
|
|
{
|
|
struct mt_gpt_device *dev = id_to_dev(GPT_CLKEVT_ID);
|
|
|
|
__gpt_stop(dev);
|
|
__gpt_set_mode(dev, GPT_ONE_SHOT);
|
|
/* __gpt_enable_irq(dev); */
|
|
/* __gpt_clrcnt_and_start(dev); */
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mt_gpt_set_periodic(struct clock_event_device *clk)
|
|
{
|
|
struct mt_gpt_device *dev = id_to_dev(GPT_CLKEVT_ID);
|
|
|
|
__gpt_stop(dev);
|
|
__gpt_set_mode(dev, GPT_REPEAT);
|
|
__gpt_enable_irq(dev);
|
|
__gpt_wait_clrcnt_then_start(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static cycle_t mt_gpt_read(struct clocksource *cs)
|
|
{
|
|
cycle_t cycles;
|
|
unsigned int cnt[2] = {0, 0};
|
|
struct mt_gpt_device *dev = id_to_dev(GPT_CLKSRC_ID);
|
|
|
|
__gpt_get_cnt(dev, cnt);
|
|
|
|
if (GPT_CLKSRC_ID != GPT6) {
|
|
/*
|
|
* force do mask for high 32-bit to avoid unpredicted
|
|
* alignment
|
|
*/
|
|
cycles = (GPT_BIT_MASK_L & (cycle_t) (cnt[0]));
|
|
} else {
|
|
cycles = (GPT_BIT_MASK_H & (((cycle_t) (cnt[1])) << 32)) |
|
|
(GPT_BIT_MASK_L&((cycle_t) (cnt[0])));
|
|
}
|
|
|
|
return cycles;
|
|
}
|
|
|
|
static void clkevt_handler(unsigned long data)
|
|
{
|
|
struct clock_event_device *evt = (struct clock_event_device *)data;
|
|
|
|
#ifdef _MTK_TIMER_DBG_AEE_DUMP
|
|
gpt_time_clkevt_handler_entry = sched_clock();
|
|
#endif
|
|
|
|
#if defined(CONFIG_MTK_TIMER_DEBUG) && \
|
|
!defined(CONFIG_MTK_TIMER_BC_IRQ_FORCE_CPU0)
|
|
int cpu;
|
|
int err = 0;
|
|
|
|
cpu = mt_irq_dump_cpu(evt->irq);
|
|
|
|
if (cpu < 0) {
|
|
pr_info("[mt_gpt] invalid irq query! ret %d\n", cpu);
|
|
err = 1;
|
|
} else {
|
|
if (cpu != smp_processor_id()) {
|
|
pr_info("[mt_gpt] wrong irq! irq_cpu %d, cur_cpu %d\n",
|
|
cpu, smp_processor_id());
|
|
err = 1;
|
|
}
|
|
|
|
if (cpu != evt->irq_affinity_on) {
|
|
pr_info("[mt_gpt] wrong affinity! irq_cpu %d, affinity %d\n",
|
|
cpu, evt->irq_affinity_on);
|
|
err = 1;
|
|
}
|
|
}
|
|
|
|
if (!err)
|
|
mt_irq_dump_status(evt->irq);
|
|
#endif
|
|
|
|
evt->event_handler(evt);
|
|
|
|
#ifdef _MTK_TIMER_DBG_AEE_DUMP
|
|
gpt_time_clkevt_handler_exit = sched_clock();
|
|
#endif
|
|
}
|
|
|
|
static inline void setup_clksrc(u32 freq)
|
|
{
|
|
struct clocksource *cs = &gpt_clocksource;
|
|
struct mt_gpt_device *dev = id_to_dev(GPT_CLKSRC_ID);
|
|
|
|
pr_info("setup_clksrc1: dev->base_addr=0x%lx GPT2_CON=0x%x\n",
|
|
(unsigned long)dev->base_addr, __raw_readl(dev->base_addr));
|
|
|
|
/* add GPT_NOIRQEN flag to avoid irq asserted because
|
|
* clksrc is not used
|
|
*/
|
|
setup_gpt_dev_locked(dev, GPT_FREE_RUN, GPT_CLK_SRC_SYS, GPT_CLK_DIV_1,
|
|
0, NULL, GPT_NOIRQEN);
|
|
|
|
/* clocksource_register(cs); */
|
|
clocksource_register_hz(cs, freq);
|
|
|
|
pr_info("setup_clksrc2: dev->base_addr=0x%lx GPT2_CON=0x%x\n",
|
|
(unsigned long)dev->base_addr, __raw_readl(dev->base_addr));
|
|
}
|
|
|
|
static inline void setup_clkevt(u32 freq, int irq)
|
|
{
|
|
unsigned int cmp[2];
|
|
unsigned int clkdiv;
|
|
struct clock_event_device *evt = &gpt_clockevent;
|
|
struct mt_gpt_device *dev = id_to_dev(GPT_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(3, evt);
|
|
evt->cpumask = cpu_possible_mask;
|
|
|
|
if (freq == 13000000)
|
|
clkdiv = GPT_CLK_SRC_SYS;
|
|
else
|
|
clkdiv = GPT_CLK_SRC_RTC;
|
|
|
|
/*
|
|
* 1. Use always-on 32K clock since 13M/26M clock will be
|
|
* disabled during SODI
|
|
* 2. Configure this device as ONESHOT mode as tick broadcast device.
|
|
*/
|
|
setup_gpt_dev_locked(dev, GPT_ONE_SHOT, clkdiv, GPT_CLK_DIV_1,
|
|
freq / HZ, clkevt_handler, GPT_ISR);
|
|
|
|
__gpt_get_cmp(dev, cmp);
|
|
|
|
pr_info("apxgpt%d: clkdiv=%d, cmp=%d, hz=%d, freq=%d\n",
|
|
GPT_CLKEVT_ID + 1, clkdiv, cmp[0], HZ, freq);
|
|
|
|
clockevents_register_device(evt);
|
|
}
|
|
|
|
static void __init mt_gpt_init_acao(struct device_node *node)
|
|
{
|
|
u32 freq;
|
|
struct clk *clk_evt;
|
|
|
|
/* inquiry clk_evt, freq is RTC_CLK_RATE (32KHz) */
|
|
|
|
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");
|
|
|
|
#ifdef CONFIG_MTK_TIMER_BC_IRQ_FORCE_CPU0
|
|
irq_force_affinity(gpt_timers.tmr_irq, cpumask_of(0));
|
|
#endif
|
|
|
|
setup_clkevt(freq, gpt_timers.tmr_irq);
|
|
|
|
pr_info("acao clkevt, freq=%d\n", freq);
|
|
|
|
}
|
|
|
|
static void release_gpt_dev_locked(struct mt_gpt_device *dev)
|
|
{
|
|
__gpt_reset(dev);
|
|
|
|
handlers[dev->id] = noop;
|
|
dev->func = NULL;
|
|
|
|
dev->flags = 0;
|
|
}
|
|
|
|
/* gpt is counting or not */
|
|
static int __gpt_get_status(struct mt_gpt_device *dev)
|
|
{
|
|
return !!(__raw_readl(dev->base_addr + GPT_CON) & GPT_CON_ENABLE);
|
|
}
|
|
|
|
/********************** export area *********************/
|
|
int request_gpt(unsigned int id, unsigned int mode, unsigned int clksrc,
|
|
unsigned int clkdiv, unsigned int cmp,
|
|
void (*func)(unsigned long), unsigned int flags)
|
|
{
|
|
unsigned long save_flags;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (dev->flags & GPT_IN_USE) {
|
|
pr_info("%s: GPT%d is in use!\n", __func__, (id + 1));
|
|
return -EBUSY;
|
|
}
|
|
|
|
gpt_update_lock(save_flags);
|
|
setup_gpt_dev_locked(dev, mode, clksrc, clkdiv, cmp, func, flags);
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(request_gpt);
|
|
|
|
int free_gpt(unsigned int id)
|
|
{
|
|
unsigned long save_flags;
|
|
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (!(dev->flags & GPT_IN_USE))
|
|
return 0;
|
|
|
|
gpt_update_lock(save_flags);
|
|
release_gpt_dev_locked(dev);
|
|
gpt_update_unlock(save_flags);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(free_gpt);
|
|
|
|
int start_gpt(unsigned int id)
|
|
{
|
|
unsigned long save_flags;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (!(dev->flags & GPT_IN_USE)) {
|
|
pr_info("%s: GPT%d is not in use!\n", __func__, id);
|
|
return -EBUSY;
|
|
}
|
|
|
|
gpt_update_lock(save_flags);
|
|
__gpt_clrcnt(dev);
|
|
__gpt_start(dev);
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(start_gpt);
|
|
|
|
int stop_gpt(unsigned int id)
|
|
{
|
|
unsigned long save_flags;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (!(dev->flags & GPT_IN_USE)) {
|
|
pr_info("%s: GPT%d is not in use!\n", __func__, id);
|
|
return -EBUSY;
|
|
}
|
|
|
|
gpt_update_lock(save_flags);
|
|
__gpt_stop(dev);
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(stop_gpt);
|
|
|
|
int restart_gpt(unsigned int id)
|
|
{
|
|
unsigned long save_flags;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (!(dev->flags & GPT_IN_USE)) {
|
|
pr_info("%s: GPT%d is not in use!\n", __func__, id);
|
|
return -EBUSY;
|
|
}
|
|
|
|
gpt_update_lock(save_flags);
|
|
__gpt_start(dev);
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(restart_gpt);
|
|
|
|
int gpt_is_counting(unsigned int id)
|
|
{
|
|
unsigned long save_flags;
|
|
int is_counting;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (!(dev->flags & GPT_IN_USE)) {
|
|
pr_info("%s: GPT%d is not in use!\n", __func__, id);
|
|
return -EBUSY;
|
|
}
|
|
|
|
gpt_update_lock(save_flags);
|
|
is_counting = __gpt_get_status(dev);
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return is_counting;
|
|
}
|
|
EXPORT_SYMBOL(gpt_is_counting);
|
|
|
|
int gpt_set_cmp(unsigned int id, unsigned int val)
|
|
{
|
|
unsigned long save_flags;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (dev->mode == GPT_FREE_RUN)
|
|
return -EINVAL;
|
|
|
|
gpt_update_lock(save_flags);
|
|
__gpt_set_cmp(dev, val, 0);
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(gpt_set_cmp);
|
|
|
|
int gpt_get_cmp(unsigned int id, unsigned int *ptr)
|
|
{
|
|
unsigned long save_flags;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev || !ptr)
|
|
return -EINVAL;
|
|
|
|
gpt_update_lock(save_flags);
|
|
__gpt_get_cmp(dev, ptr);
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(gpt_get_cmp);
|
|
|
|
int gpt_get_cnt(unsigned int id, unsigned int *ptr)
|
|
{
|
|
unsigned long save_flags;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev || !ptr)
|
|
return -EINVAL;
|
|
|
|
if (!(dev->features & GPT_FEAT_64_BIT)) {
|
|
__gpt_get_cnt(dev, ptr);
|
|
} else {
|
|
gpt_update_lock(save_flags);
|
|
__gpt_get_cnt(dev, ptr);
|
|
gpt_update_unlock(save_flags);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(gpt_get_cnt);
|
|
|
|
u64 mtk_timer_get_cnt(u8 timer)
|
|
{
|
|
unsigned long save_flags;
|
|
unsigned int val[2] = {0, 0};
|
|
u64 cnt = 0;
|
|
struct mt_gpt_device *dev = id_to_dev(timer - 1);
|
|
|
|
if (!dev || timer <= 0)
|
|
return -EINVAL;
|
|
|
|
if (!(dev->features & GPT_FEAT_64_BIT)) {
|
|
__gpt_get_cnt(dev, val);
|
|
cnt = (((u64)val[1] << 32) | (u64)val[0]);
|
|
} else {
|
|
gpt_update_lock(save_flags);
|
|
__gpt_get_cnt(dev, val);
|
|
gpt_update_unlock(save_flags);
|
|
cnt = ((u64)val[0]) & 0x00000000FFFFFFFF;
|
|
}
|
|
|
|
return cnt;
|
|
}
|
|
|
|
int gpt_check_irq(unsigned int id)
|
|
{
|
|
unsigned int mask = 0x1 << id;
|
|
unsigned int status = __raw_readl(GPT_IRQSTA);
|
|
|
|
return (status & mask) ? 1 : 0;
|
|
}
|
|
EXPORT_SYMBOL(gpt_check_irq);
|
|
|
|
int gpt_check_and_ack_irq(unsigned int id)
|
|
{
|
|
unsigned int mask = 0x1 << id;
|
|
unsigned int status = __raw_readl(GPT_IRQSTA);
|
|
|
|
if (status & mask) {
|
|
mt_reg_sync_writel(mask, GPT_IRQACK);
|
|
return 1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(gpt_check_and_ack_irq);
|
|
|
|
unsigned int gpt_boot_time(void)
|
|
{
|
|
return boot_time_value;
|
|
}
|
|
EXPORT_SYMBOL(gpt_boot_time);
|
|
|
|
int gpt_set_clk(unsigned int id, unsigned int clksrc, unsigned int clkdiv)
|
|
{
|
|
unsigned long save_flags;
|
|
struct mt_gpt_device *dev = id_to_dev(id);
|
|
|
|
if (!dev)
|
|
return -EINVAL;
|
|
|
|
if (!(dev->flags & GPT_IN_USE)) {
|
|
pr_info("%s: GPT%d is not in use!\n", __func__, id);
|
|
return -EBUSY;
|
|
}
|
|
|
|
gpt_update_lock(save_flags);
|
|
__gpt_stop(dev);
|
|
__gpt_set_clk(dev, clksrc, clkdiv);
|
|
__gpt_start(dev);
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(gpt_set_clk);
|
|
|
|
static int __init mt_gpt_init(struct device_node *node)
|
|
{
|
|
int i;
|
|
unsigned long save_flags;
|
|
|
|
gpt_update_lock(save_flags);
|
|
|
|
/* Setup IRQ numbers */
|
|
gpt_timers.tmr_irq = irq_of_parse_and_map(node, 0);
|
|
|
|
/* Setup IO addresses */
|
|
gpt_timers.tmr_regs = of_iomap(node, 0);
|
|
|
|
pr_info("base=0x%lx, irq=%d\n",
|
|
(unsigned long)gpt_timers.tmr_regs, gpt_timers.tmr_irq);
|
|
|
|
/* setup gpt itself */
|
|
gpt_devs_init();
|
|
|
|
for (i = 0; i < NR_GPTS; i++)
|
|
__gpt_reset(&gpt_devs[i]);
|
|
|
|
setup_irq(gpt_timers.tmr_irq, &gpt_irq);
|
|
|
|
mt_gpt_init_acao(node);
|
|
|
|
/* record the time when init GPT */
|
|
boot_time_value = gpt_boot_up_time();
|
|
|
|
gpt_update_unlock(save_flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
CLOCKSOURCE_OF_DECLARE(mtk_apxgpt, APXGPT_OF_COMPTIBLE_NAME, mt_gpt_init);
|
|
|
|
#endif
|
|
|
|
|