kernel_samsung_a34x-permissive/drivers/clocksource/mtk_apxgpt.c

1057 lines
24 KiB
C
Raw Normal View History

// 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