330 lines
8.2 KiB
C
330 lines
8.2 KiB
C
/*
|
|
* Copyright (c) 2016-2017, NVIDIA CORPORATION. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <linux/clockchips.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <soc/tegra/chip-id.h>
|
|
#include <linux/tick.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <linux/syscore_ops.h>
|
|
#include <linux/version.h>
|
|
|
|
#define TMRCR 0x000
|
|
#define TMRSR 0x004
|
|
#define TMRCSSR 0x008
|
|
#define TKEIE 0x100
|
|
|
|
|
|
struct tegra186_tke;
|
|
|
|
struct tegra186_tmr {
|
|
struct clock_event_device evt;
|
|
u64 tmr_index;
|
|
u64 cpu_index;
|
|
u32 freq;
|
|
char name[20];
|
|
void __iomem *reg_base;
|
|
struct tegra186_tke *tke;
|
|
};
|
|
|
|
struct tegra186_tke {
|
|
void __iomem *reg_base;
|
|
struct tegra186_tmr tegra186_tmr[CONFIG_NR_CPUS];
|
|
};
|
|
|
|
static struct tegra186_tke *tke;
|
|
|
|
static int tegra186_timer_set_next_event(unsigned long cycles,
|
|
struct clock_event_device *evt)
|
|
{
|
|
struct tegra186_tmr *tmr;
|
|
tmr = container_of(evt, struct tegra186_tmr, evt);
|
|
__raw_writel((1 << 31) /* EN=1, enable timer */
|
|
| ((cycles > 1) ? (cycles - 1) : 0), /* n+1 scheme */
|
|
tmr->reg_base + TMRCR);
|
|
return 0;
|
|
}
|
|
|
|
static inline void _shutdown(struct tegra186_tmr *tmr)
|
|
{
|
|
__raw_writel(0 << 31, /* EN=0, disable timer */
|
|
tmr->reg_base + TMRCR);
|
|
__raw_writel(1 << 30, /* INTR_CLR */
|
|
tmr->reg_base + TMRSR);
|
|
}
|
|
|
|
static int tegra186_timer_shutdown(struct clock_event_device *evt)
|
|
{
|
|
struct tegra186_tmr *tmr;
|
|
tmr = container_of(evt, struct tegra186_tmr, evt);
|
|
|
|
_shutdown(tmr);
|
|
return 0;
|
|
}
|
|
|
|
static int tegra186_timer_set_periodic(struct clock_event_device *evt)
|
|
{
|
|
struct tegra186_tmr *tmr = container_of(evt, struct tegra186_tmr, evt);
|
|
|
|
_shutdown(tmr);
|
|
__raw_writel((1 << 31) /* EN=1, enable timer */
|
|
| (1 << 30) /* PER=1, periodic mode */
|
|
| ((tmr->freq / HZ) - 1), /* PTV, preset value*/
|
|
tmr->reg_base + TMRCR);
|
|
return 0;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0)
|
|
static void tegra186_timer_set_mode(enum clock_event_mode mode,
|
|
struct clock_event_device *evt)
|
|
{
|
|
switch (mode) {
|
|
case CLOCK_EVT_MODE_PERIODIC:
|
|
tegra186_timer_set_periodic(evt);
|
|
break;
|
|
case CLOCK_EVT_MODE_ONESHOT:
|
|
case CLOCK_EVT_MODE_UNUSED:
|
|
case CLOCK_EVT_MODE_SHUTDOWN:
|
|
case CLOCK_EVT_MODE_RESUME:
|
|
tegra186_timer_shutdown(evt);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static irqreturn_t tegra186_timer_isr(int irq, void *dev_id)
|
|
{
|
|
struct tegra186_tmr *tmr;
|
|
|
|
tmr = (struct tegra186_tmr *) dev_id;
|
|
__raw_writel(1 << 30, /* INTR_CLR */
|
|
tmr->reg_base + TMRSR);
|
|
tmr->evt.event_handler(&tmr->evt);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static int tegra186_timer_setup(unsigned int cpu)
|
|
{
|
|
struct tegra186_tmr *tmr = &tke->tegra186_tmr[cpu];
|
|
|
|
clockevents_config_and_register(&tmr->evt, tmr->freq,
|
|
1, /* min */
|
|
0x1fffffff); /* 29 bits */
|
|
#ifdef CONFIG_SMP
|
|
if (irq_force_affinity(tmr->evt.irq, cpumask_of(cpu))) {
|
|
pr_err("%s: cannot set irq %d affinity to CPU%d\n",
|
|
__func__, tmr->evt.irq, cpu);
|
|
BUG();
|
|
}
|
|
#endif
|
|
enable_irq(tmr->evt.irq);
|
|
return 0;
|
|
}
|
|
|
|
static int tegra186_timer_enable_irq(unsigned int cpu)
|
|
{
|
|
struct tegra186_tmr *tmr = &tke->tegra186_tmr[cpu];
|
|
|
|
#ifdef CONFIG_SMP
|
|
if (irq_force_affinity(tmr->evt.irq, cpumask_of(cpu))) {
|
|
pr_err("%s: cannot set irq %d affinity to CPU%d\n",
|
|
__func__, tmr->evt.irq, cpu);
|
|
BUG();
|
|
}
|
|
#endif
|
|
enable_irq(tmr->evt.irq);
|
|
return 0;
|
|
}
|
|
|
|
static int tegra186_timer_stop(unsigned int cpu)
|
|
{
|
|
struct tegra186_tmr *tmr = &tke->tegra186_tmr[cpu];
|
|
_shutdown(tmr);
|
|
disable_irq_nosync(tmr->evt.irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int tegra186_timer_suspend(void)
|
|
{
|
|
int cpu = smp_processor_id();
|
|
struct tegra186_tmr *tmr = &tke->tegra186_tmr[cpu];
|
|
|
|
_shutdown(tmr);
|
|
disable_irq_nosync(tmr->evt.irq);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
|
|
static int tegra186_timer_cpu_notify(struct notifier_block *self,
|
|
unsigned long action, void *hcpu)
|
|
{
|
|
switch (action & ~CPU_TASKS_FROZEN) {
|
|
case CPU_STARTING:
|
|
tegra186_timer_setup(smp_processor_id());
|
|
break;
|
|
case CPU_DYING:
|
|
tegra186_timer_stop(smp_processor_id());
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block tegra186_timer_cpu_nb = {
|
|
.notifier_call = tegra186_timer_cpu_notify,
|
|
};
|
|
#endif
|
|
|
|
static void tegra186_timer_resume(void)
|
|
{
|
|
int cpu;
|
|
struct tegra186_tmr *tmr;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
tmr = &tke->tegra186_tmr[cpu];
|
|
|
|
/* Configure TSC as the TKE source */
|
|
__raw_writel(2, tmr->reg_base + TMRCSSR);
|
|
|
|
__raw_writel(1 << tmr->tmr_index, tke->reg_base
|
|
+ TKEIE + 4 * tmr->tmr_index);
|
|
}
|
|
|
|
cpu = smp_processor_id();
|
|
tegra186_timer_enable_irq(cpu);
|
|
}
|
|
|
|
static struct syscore_ops tegra186_timer_syscore_ops = {
|
|
.suspend = tegra186_timer_suspend,
|
|
.resume = tegra186_timer_resume,
|
|
};
|
|
|
|
static void __init tegra186_timer_init(struct device_node *np)
|
|
{
|
|
int cpu;
|
|
struct tegra186_tmr *tmr;
|
|
u32 tmr_count;
|
|
u32 freq;
|
|
int irq_count;
|
|
unsigned long tmr_index, irq_index;
|
|
|
|
/* Allocate the driver struct */
|
|
tke = vmalloc(sizeof(*tke));
|
|
BUG_ON(!tke);
|
|
memset(tke, 0, sizeof(*tke));
|
|
|
|
/* MAp MMIO */
|
|
tke->reg_base = of_iomap(np, 0);
|
|
if (!tke->reg_base) {
|
|
pr_err("%s: can't map timer registers\n", __func__);
|
|
BUG();
|
|
}
|
|
|
|
/* Read the parameters */
|
|
BUG_ON(of_property_read_u32(np, "tmr-count", &tmr_count));
|
|
irq_count = of_irq_count(np);
|
|
|
|
BUG_ON(of_property_read_u32(np, "clock-frequency", &freq));
|
|
|
|
tmr_index = 0;
|
|
for_each_possible_cpu(cpu) {
|
|
tmr = &tke->tegra186_tmr[cpu];
|
|
tmr->tke = tke;
|
|
tmr->tmr_index = tmr_index;
|
|
tmr->freq = freq;
|
|
|
|
/* Allocate a TMR */
|
|
BUG_ON(tmr_index >= tmr_count);
|
|
tmr->reg_base = tke->reg_base + 0x10000 * (tmr_index + 1);
|
|
|
|
/* Allocate an IRQ */
|
|
irq_index = tmr_index;
|
|
BUG_ON(irq_index >= irq_count);
|
|
/* Program TKEIE to map TMR to the right IRQ */
|
|
__raw_writel(1 << tmr_index, tke->reg_base
|
|
+ TKEIE + 4 * irq_index);
|
|
tmr->evt.irq = irq_of_parse_and_map(np, irq_index);
|
|
BUG_ON(!tmr->evt.irq);
|
|
|
|
/* Configure OSC as the TKE source */
|
|
__raw_writel(1, tmr->reg_base + TMRCSSR);
|
|
|
|
snprintf(tmr->name, sizeof(tmr->name), "tegra186_timer%d", cpu);
|
|
tmr->evt.name = tmr->name;
|
|
tmr->evt.cpumask = cpumask_of(cpu);
|
|
tmr->evt.features = CLOCK_EVT_FEAT_PERIODIC |
|
|
CLOCK_EVT_FEAT_ONESHOT;
|
|
tmr->evt.set_next_event = tegra186_timer_set_next_event;
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 2, 0)
|
|
tmr->evt.set_mode = tegra186_timer_set_mode;
|
|
#else
|
|
tmr->evt.set_state_shutdown = tegra186_timer_shutdown;
|
|
tmr->evt.set_state_periodic = tegra186_timer_set_periodic;
|
|
tmr->evt.set_state_oneshot = tegra186_timer_shutdown;
|
|
tmr->evt.tick_resume = tegra186_timer_shutdown;
|
|
#endif
|
|
/* want to be preferred over arch timers */
|
|
tmr->evt.rating = 460;
|
|
irq_set_status_flags(tmr->evt.irq, IRQ_NOAUTOEN | IRQ_PER_CPU);
|
|
if (request_irq(tmr->evt.irq, tegra186_timer_isr,
|
|
IRQF_TIMER | IRQF_TRIGGER_HIGH
|
|
| IRQF_NOBALANCING, tmr->name, tmr)) {
|
|
pr_err("%s: cannot setup irq %d for CPU%d\n",
|
|
__func__, tmr->evt.irq, cpu);
|
|
BUG();
|
|
}
|
|
tmr_index++;
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
|
|
/* boot cpu is online */
|
|
tegra186_timer_setup(0);
|
|
|
|
if (register_cpu_notifier(&tegra186_timer_cpu_nb)) {
|
|
pr_err("%s: cannot setup CPU notifier\n", __func__);
|
|
BUG();
|
|
}
|
|
#else
|
|
cpuhp_setup_state(CPUHP_AP_TEGRA_TIMER_STARTING,
|
|
"AP_TEGRA_TIMER_STARTING", tegra186_timer_setup,
|
|
tegra186_timer_stop);
|
|
#endif
|
|
|
|
register_syscore_ops(&tegra186_timer_syscore_ops);
|
|
}
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)
|
|
#define tegra186_timer_init_func tegra186_timer_init
|
|
#else
|
|
static int __init tegra186_timer_init_ret(struct device_node *np)
|
|
{
|
|
tegra186_timer_init(np);
|
|
return 0;
|
|
}
|
|
#define tegra186_timer_init_func tegra186_timer_init_ret
|
|
#endif
|
|
|
|
CLOCKSOURCE_OF_DECLARE(tegra186_timer, "nvidia,tegra186-timer",
|
|
tegra186_timer_init_func);
|