kernel_samsung_a53x/drivers/clocksource/exynos_mct_v2.c
2024-06-15 16:02:09 -03:00

549 lines
14 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0-only
/* drivers/clocksource/exynos_mct_v2.c
*
* Copyright (c) 2021 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Exynos MCT(Multi-Core Timer) support
*/
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/cpu.h>
#include <linux/delay.h>
#include <linux/percpu.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/clocksource.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/sched/clock.h>
#include "exynos_mct_v2.h"
static void __iomem *reg_base;
static unsigned long osc_clk_rate;
static unsigned int mct_int_type;
static int mct_irqs[MCT_NR_COMPS];
static DEFINE_SPINLOCK(comp_lock);
static u64 exynos_mct_start;
u64 exynos_get_mct_start(void)
{
return exynos_mct_start;
}
EXPORT_SYMBOL_GPL(exynos_get_mct_start);
static void exynos_mct_set_compensation(unsigned long osc, unsigned long rtc)
{
unsigned int osc_rtc;
unsigned int osc_rtc_round;
unsigned int compen_v;
unsigned int sign;
osc_rtc = (unsigned int) (osc * 1000 / rtc);
osc_rtc_round = (osc_rtc + 500) / 1000 * 1000;
sign = (osc_rtc_round > osc_rtc) ? 0: 1;
compen_v = sign ? (osc_rtc - osc_rtc_round):(osc_rtc_round - osc_rtc);
compen_v = (compen_v + 5) / 10;
compen_v |= sign << 31;
osc_rtc_round /= 1000;
pr_info("MCT: osc-%lu rtc-%lu incr_rtcclk:0x%08x compen_v:0x%08x\n",
osc, rtc, osc_rtc_round, compen_v);
writel_relaxed(osc_rtc_round, reg_base + EXYNOS_MCT_MCT_INCR_RTCCLK);
writel_relaxed(compen_v, reg_base + EXYNOS_MCT_COMPENSATE_VALUE);
}
/* Clocksource handling */
static void exynos_mct_frc_start(void)
{
writel_relaxed(MCT_FRC_ENABLE, reg_base + EXYNOS_MCT_MCT_FRC_ENABLE);
}
/**
* exynos_read_count_32 - Read the lower 32-bits of the global counter
*
* This will read just the lower 32-bits of the global counter.
*
* Returns the number of cycles in the global counter (lower 32 bits).
*/
static u32 exynos_read_count_32(void)
{
return readl_relaxed(reg_base + EXYNOS_MCT_CNT_L);
}
static u64 exynos_frc_read(struct clocksource *cs)
{
return exynos_read_count_32();
}
static struct clocksource mct_frc = {
.name = "mct-frc",
.rating = 350, /* use value lower than ARM arch timer */
.read = exynos_frc_read,
.mask = CLOCKSOURCE_MASK(32),
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
static int exynos_clocksource_init(void)
{
if (clocksource_register_hz(&mct_frc, osc_clk_rate))
panic("%s: can't register clocksource\n", mct_frc.name);
return 0;
}
static void exynos_mct_comp_stop(struct mct_clock_event_device *mevt)
{
unsigned int index = mevt->comp_index;
unsigned int comp_mode, comp_enable, int_enb;
unsigned long flags;
spin_lock_irqsave(&comp_lock, flags);
comp_enable = readl_relaxed(reg_base + EXYNOS_MCT_COMP_ENABLE);
comp_enable &= ~(0x1 << index);
writel_relaxed(comp_enable, reg_base + EXYNOS_MCT_COMP_ENABLE);
mevt->disable_time = readl_relaxed(reg_base + EXYNOS_MCT_CNT_L);
comp_mode = readl_relaxed(reg_base + EXYNOS_MCT_COMP_MODE);
comp_mode &= ~(0x1 << index);
writel_relaxed(comp_mode, reg_base + EXYNOS_MCT_COMP_MODE);
int_enb = readl_relaxed(reg_base + EXYNOS_MCT_INT_ENB);
int_enb &= ~(0x1 << index);
writel_relaxed(int_enb, reg_base + EXYNOS_MCT_INT_ENB);
writel_relaxed(0, reg_base + EXYNOS_MCT_COMP_L(index));
writel_relaxed(0, reg_base + EXYNOS_MCT_COMP_U(index));
writel_relaxed(0, reg_base + EXYNOS_MCT_PREPARE_CSTAT);
writel_relaxed(0x1 << mevt->comp_index, reg_base + EXYNOS_MCT_INT_CSTAT);
spin_unlock_irqrestore(&comp_lock, flags);
}
inline unsigned int count_diff(unsigned int pre_count, unsigned int cur_count) {
if (cur_count >= pre_count)
return cur_count - pre_count;
else
return (0xffffffff - pre_count) + cur_count;
}
static void exynos_mct_comp_start(struct mct_clock_event_device *mevt,
bool periodic, unsigned long cycles)
{
unsigned int index = mevt->comp_index;
unsigned int comp_mode, comp_enable, int_enb;
unsigned int pre_comp_l, pre_comp_u, comp_l, comp_u;
unsigned int pre_cnt_l, cnt_l, cnt_u;
unsigned int loop_cnt;
unsigned long flags;
int retry = 0;
do {
comp_enable = readl_relaxed(reg_base + EXYNOS_MCT_COMP_ENABLE);
if (comp_enable & (0x1 << index))
exynos_mct_comp_stop(mevt);
pre_comp_l = readl_relaxed(reg_base + EXYNOS_MCT_COMP_L(index));
pre_comp_u = readl_relaxed(reg_base + EXYNOS_MCT_COMP_U(index));
spin_lock_irqsave(&comp_lock, flags);
if (periodic) {
comp_mode = readl_relaxed(reg_base + EXYNOS_MCT_COMP_MODE);
comp_mode |= 0x1 << index;
writel_relaxed(comp_mode , reg_base + EXYNOS_MCT_COMP_MODE);
}
writel_relaxed(cycles, reg_base + EXYNOS_MCT_COMP_PERIOD(index));
int_enb = readl_relaxed(reg_base + EXYNOS_MCT_INT_ENB);
int_enb |= 0x1 << index;
writel_relaxed(int_enb, reg_base + EXYNOS_MCT_INT_ENB);
spin_unlock_irqrestore(&comp_lock, flags);
/*
* Wait until WAIT_MCT_CNT cycles have passed since COMP is disabled.
*/
loop_cnt = 0;
pre_cnt_l = mevt->disable_time;
do {
cnt_l = readl_relaxed(reg_base + EXYNOS_MCT_CNT_L);
loop_cnt++;
} while((count_diff(pre_cnt_l, cnt_l) < WAIT_MCT_CNT) &&
(loop_cnt < TIMEOUT_LOOP_COUNT));
if (TIMEOUT_LOOP_COUNT == loop_cnt)
pr_warn("MCT(comp%d) disable timeout\n", index);
spin_lock_irqsave(&comp_lock, flags);
comp_enable = readl_relaxed(reg_base + EXYNOS_MCT_COMP_ENABLE);
comp_enable |= 0x1 << index;
writel_relaxed(comp_enable , reg_base + EXYNOS_MCT_COMP_ENABLE);
spin_unlock_irqrestore(&comp_lock, flags);
pre_cnt_l = readl_relaxed(reg_base + EXYNOS_MCT_CNT_L);
/*
* Wait for the COMP_U/L to change
*/
loop_cnt = 0;
do {
comp_l = readl_relaxed(reg_base + EXYNOS_MCT_COMP_L(index));
comp_u = readl_relaxed(reg_base + EXYNOS_MCT_COMP_U(index));
loop_cnt++;
if (comp_l != pre_comp_l || comp_u != pre_comp_u) {
return;
}
} while(TIMEOUT_LOOP_COUNT > loop_cnt);
retry++;
cnt_l = readl_relaxed(reg_base + EXYNOS_MCT_CNT_L);
cnt_u = readl_relaxed(reg_base + EXYNOS_MCT_CNT_U);
pr_warn("MCT(comp%d) enable timeout "
"(COMP_U/L:0x%08x, 0x%08x, pre COMP_U/L:0x%08x, 0x%08x) "
"(CNT_U/L:0x%08x, 0x%08x) "
"pre CNT_L:0x%08x "
"retry:%d/%d\n",
index,
comp_u, comp_l,
pre_comp_u, pre_comp_l,
cnt_u, cnt_l,
pre_cnt_l,
retry, RETRY_CNT);
} while(retry <= RETRY_CNT);
panic("MCT(comp%d) hangs (periodic:%d cycles:%lu)\n", index, periodic, cycles);
}
static int exynos_comp_set_next_event(unsigned long cycles,
struct clock_event_device *evt)
{
struct mct_clock_event_device *mevt;
mevt = container_of(evt, struct mct_clock_event_device, evt);
exynos_mct_comp_start(mevt, false, cycles);
return 0;
}
static int mct_set_state_shutdown(struct clock_event_device *evt)
{
struct mct_clock_event_device *mevt;
mevt = container_of(evt, struct mct_clock_event_device, evt);
exynos_mct_comp_stop(mevt);
return 0;
}
static int mct_set_state_periodic(struct clock_event_device *evt)
{
unsigned long cycles_per_jiffy;
struct mct_clock_event_device *mevt;
mevt = container_of(evt, struct mct_clock_event_device, evt);
cycles_per_jiffy = (((unsigned long long)NSEC_PER_SEC / HZ * evt->mult)
>> evt->shift);
exynos_mct_comp_start(mevt, true, cycles_per_jiffy);
return 0;
}
static struct mct_clock_event_device mct_comp_device = {
.evt = {
.name = "mct-comp0",
.features = CLOCK_EVT_FEAT_PERIODIC |
CLOCK_EVT_FEAT_ONESHOT,
.rating = 250,
.set_next_event = exynos_comp_set_next_event,
.set_state_periodic = mct_set_state_periodic,
.set_state_shutdown = mct_set_state_shutdown,
.set_state_oneshot = mct_set_state_shutdown,
.set_state_oneshot_stopped = mct_set_state_shutdown,
.tick_resume = mct_set_state_shutdown,
},
.name = "mct-comp0",
.comp_index = MCT_COMP0,
};
static irqreturn_t exynos_mct_comp_isr(int irq, void *dev_id)
{
struct mct_clock_event_device *mevt = dev_id;
struct clock_event_device *evt = &mevt->evt;
unsigned long flags;
spin_lock_irqsave(&comp_lock, flags);
writel_relaxed(0, reg_base + EXYNOS_MCT_PREPARE_CSTAT);
writel_relaxed(0x1 << mevt->comp_index, reg_base + EXYNOS_MCT_INT_CSTAT);
spin_unlock_irqrestore(&comp_lock, flags);
evt->event_handler(evt);
return IRQ_HANDLED;
}
static int exynos_clockevent_init(void)
{
mct_comp_device.evt.cpumask = cpumask_of(0);
clockevents_config_and_register(&mct_comp_device.evt, osc_clk_rate,
0xf, 0xffffffff);
if (request_irq(mct_irqs[MCT_COMP0], exynos_mct_comp_isr,
IRQF_TIMER | IRQF_IRQPOLL, "mct_comp0_irq",
&mct_comp_device))
pr_err("%s: request_irq() failed\n", "mct_comp0_irq");
return 0;
}
static DEFINE_PER_CPU(struct mct_clock_event_device, percpu_mct_tick);
static int exynos_mct_starting_cpu(unsigned int cpu)
{
struct mct_clock_event_device *mevt =
per_cpu_ptr(&percpu_mct_tick, cpu);
struct clock_event_device *evt = &mevt->evt;
struct irq_desc *desc = irq_to_desc(evt->irq);
snprintf(mevt->name, sizeof(mevt->name), "mct_comp%d", MCT_COMP4 + cpu);
evt->name = mevt->name;
evt->cpumask = cpumask_of(cpu);
evt->set_next_event = exynos_comp_set_next_event;
evt->set_state_periodic = mct_set_state_periodic;
evt->set_state_shutdown = mct_set_state_shutdown;
evt->set_state_oneshot = mct_set_state_shutdown;
evt->set_state_oneshot_stopped = mct_set_state_shutdown;
evt->tick_resume = mct_set_state_shutdown;
evt->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;
evt->rating = 500; /* use value higher than ARM arch timer */
if (evt->irq == -1)
return -EIO;
#ifdef MODULE
if (desc)
irq_do_set_affinity(irq_desc_get_irq_data(desc), cpumask_of(cpu), true);
#else
irq_force_affinity(evt->irq, cpumask_of(cpu));
#endif
enable_irq(evt->irq);
clockevents_config_and_register(evt, osc_clk_rate, 0xf, 0x7fffffff);
return 0;
}
static int exynos_mct_dying_cpu(unsigned int cpu)
{
struct mct_clock_event_device *mevt =
per_cpu_ptr(&percpu_mct_tick, cpu);
struct clock_event_device *evt = &mevt->evt;
unsigned long flags;
evt->set_state_shutdown(evt);
if (evt->irq != -1)
disable_irq_nosync(evt->irq);
spin_lock_irqsave(&comp_lock, flags);
writel_relaxed(0, reg_base + EXYNOS_MCT_PREPARE_CSTAT);
writel_relaxed(0x1 << mevt->comp_index, reg_base + EXYNOS_MCT_INT_CSTAT);
spin_unlock_irqrestore(&comp_lock, flags);
return 0;
}
static int exynos_timer_resources(struct device_node *np, void __iomem *base)
{
int err, cpu;
struct clk *mct_clk, *tick_clk, *rtc_clk;
unsigned long rtc_clk_rate;
int div;
int ret;
ret = of_property_read_u32(np, "div", &div);
if (ret || !div) {
pr_warn("MCT: fail to get the div value. set div to the default\n");
div = DEFAULT_CLK_DIV;
}
tick_clk = of_clk_get_by_name(np, "fin_pll");
if (IS_ERR(tick_clk))
panic("%s: unable to determine tick clock rate\n", __func__);
osc_clk_rate = clk_get_rate(tick_clk) / div;
mct_clk = of_clk_get_by_name(np, "mct");
if (IS_ERR(mct_clk))
panic("%s: unable to retrieve mct clock instance\n", __func__);
clk_prepare_enable(mct_clk);
rtc_clk = of_clk_get_by_name(np, "rtc");
if (IS_ERR(rtc_clk)) {
pr_warn("MCT: fail to get rtc clock. set to the default\n");
rtc_clk_rate = DEFAULT_RTC_CLK_RATE;
} else {
rtc_clk_rate = clk_get_rate(rtc_clk);
}
reg_base = base;
if (!reg_base)
panic("%s: unable to ioremap mct address space\n", __func__);
exynos_mct_set_compensation(osc_clk_rate, rtc_clk_rate);
exynos_mct_frc_start();
for_each_possible_cpu(cpu) {
int mct_irq;
struct mct_clock_event_device *pcpu_mevt;
if (MCT_COMP4 + cpu >= MCT_NR_COMPS)
break;
mct_irq = mct_irqs[MCT_COMP4 + cpu];
pcpu_mevt = per_cpu_ptr(&percpu_mct_tick, cpu);
pcpu_mevt->evt.irq = -1;
pcpu_mevt->comp_index = MCT_COMP4 + cpu;
pcpu_mevt->disable_time = 0;
irq_set_status_flags(mct_irq, IRQ_NOAUTOEN);
if (request_irq(mct_irq,
exynos_mct_comp_isr,
IRQF_TIMER | IRQF_NOBALANCING | IRQF_PERCPU,
"exynos-mct", pcpu_mevt)) {
pr_err("exynos-mct: cannot register IRQ (cpu%d)\n",
cpu);
continue;
}
pcpu_mevt->evt.irq = mct_irq;
}
/* Install hotplug callbacks which configure the timer on this CPU */
err = cpuhp_setup_state(CPUHP_AP_EXYNOS4_MCT_TIMER_STARTING,
"clockevents/exynos4/mct_timer:starting",
exynos_mct_starting_cpu,
exynos_mct_dying_cpu);
if (err)
goto out_irq;
return 0;
out_irq:
for_each_possible_cpu(cpu) {
struct mct_clock_event_device *pcpu_mevt =
per_cpu_ptr(&percpu_mct_tick, cpu);
if (pcpu_mevt->evt.irq != -1) {
free_irq(pcpu_mevt->evt.irq, pcpu_mevt);
pcpu_mevt->evt.irq = -1;
}
}
return err;
}
static int mct_init_dt(struct device_node *np, unsigned int int_type)
{
u32 nr_irqs = 0, i;
struct of_phandle_args irq;
int ret;
mct_int_type = int_type;
/* This driver uses only one global timer interrupt */
mct_irqs[MCT_COMP0] = irq_of_parse_and_map(np, MCT_COMP0);
/*
* Find out the number of local irqs specified. The local
* timer irqs are specified after the four global timer
* irqs are specified.
*/
while (of_irq_parse_one(np, nr_irqs, &irq) == 0)
nr_irqs++;
for (i = MCT_COMP4; i < nr_irqs; i++)
mct_irqs[i] = irq_of_parse_and_map(np, i);
pr_info("## exynos_timer_resources\n");
ret = exynos_timer_resources(np, of_iomap(np, 0));
if (ret)
return ret;
pr_info("## exynos_clocksource_init\n");
ret = exynos_clocksource_init();
if (ret)
return ret;
pr_info("## exynos_clockevent_init\n");
if (IS_ENABLED(CONFIG_SEC_BOOTSTAT)) {
unsigned long __osc_clk_rate;
u64 ts_msec;
exynos_mct_start = exynos_read_count_32();
__osc_clk_rate = osc_clk_rate / 1000;
if (__osc_clk_rate)
exynos_mct_start /= __osc_clk_rate;
ts_msec = local_clock();
do_div(ts_msec, 1000000);
exynos_mct_start -= ts_msec;
}
return exynos_clockevent_init();
}
static int mct_init_spi(struct device_node *np)
{
return mct_init_dt(np, MCT_INT_SPI);
}
#ifdef MODULE
static int exynos_mct_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
pr_info("exynos_mct_probe\n");
return mct_init_spi(np);
}
static const struct of_device_id exynos_mct_match_table[] = {
{ .compatible = "samsung,s5e9925-mct" },
{ }
};
MODULE_DEVICE_TABLE(of, exynos_mct_match_table);
static struct platform_driver s5e9925_mct_driver = {
.probe = exynos_mct_probe,
.driver = {
.name = "exynos-mct",
.of_match_table = exynos_mct_match_table,
},
};
module_platform_driver(s5e9925_mct_driver);
#else
TIMER_OF_DECLARE(s5e9925, "samsung,s5e9925-mct", mct_init_spi);
#endif
MODULE_DESCRIPTION("Exynos Multi Core Timer v2 driver");
MODULE_AUTHOR("Donghoon Yu <hoony.yu@samsung.com>");
MODULE_LICENSE("GPL v2");