162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci// Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <linux/init.h>
562306a36Sopenharmony_ci#include <linux/interrupt.h>
662306a36Sopenharmony_ci#include <linux/sched_clock.h>
762306a36Sopenharmony_ci#include <linux/cpu.h>
862306a36Sopenharmony_ci#include <linux/of_irq.h>
962306a36Sopenharmony_ci#include <asm/reg_ops.h>
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include "timer-of.h"
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define PTIM_CCVR	"cr<3, 14>"
1462306a36Sopenharmony_ci#define PTIM_CTLR	"cr<0, 14>"
1562306a36Sopenharmony_ci#define PTIM_LVR	"cr<6, 14>"
1662306a36Sopenharmony_ci#define PTIM_TSR	"cr<1, 14>"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_cistatic int csky_mptimer_irq;
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistatic int csky_mptimer_set_next_event(unsigned long delta,
2162306a36Sopenharmony_ci				       struct clock_event_device *ce)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	mtcr(PTIM_LVR, delta);
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci	return 0;
2662306a36Sopenharmony_ci}
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_cistatic int csky_mptimer_shutdown(struct clock_event_device *ce)
2962306a36Sopenharmony_ci{
3062306a36Sopenharmony_ci	mtcr(PTIM_CTLR, 0);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	return 0;
3362306a36Sopenharmony_ci}
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic int csky_mptimer_oneshot(struct clock_event_device *ce)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	mtcr(PTIM_CTLR, 1);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return 0;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic int csky_mptimer_oneshot_stopped(struct clock_event_device *ce)
4362306a36Sopenharmony_ci{
4462306a36Sopenharmony_ci	mtcr(PTIM_CTLR, 0);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	return 0;
4762306a36Sopenharmony_ci}
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_cistatic DEFINE_PER_CPU(struct timer_of, csky_to) = {
5062306a36Sopenharmony_ci	.flags					= TIMER_OF_CLOCK,
5162306a36Sopenharmony_ci	.clkevt = {
5262306a36Sopenharmony_ci		.rating				= 300,
5362306a36Sopenharmony_ci		.features			= CLOCK_EVT_FEAT_PERCPU |
5462306a36Sopenharmony_ci						  CLOCK_EVT_FEAT_ONESHOT,
5562306a36Sopenharmony_ci		.set_state_shutdown		= csky_mptimer_shutdown,
5662306a36Sopenharmony_ci		.set_state_oneshot		= csky_mptimer_oneshot,
5762306a36Sopenharmony_ci		.set_state_oneshot_stopped	= csky_mptimer_oneshot_stopped,
5862306a36Sopenharmony_ci		.set_next_event			= csky_mptimer_set_next_event,
5962306a36Sopenharmony_ci	},
6062306a36Sopenharmony_ci};
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_cistatic irqreturn_t csky_timer_interrupt(int irq, void *dev)
6362306a36Sopenharmony_ci{
6462306a36Sopenharmony_ci	struct timer_of *to = this_cpu_ptr(&csky_to);
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	mtcr(PTIM_TSR, 0);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	to->clkevt.event_handler(&to->clkevt);
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci	return IRQ_HANDLED;
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci/*
7462306a36Sopenharmony_ci * clock event for percpu
7562306a36Sopenharmony_ci */
7662306a36Sopenharmony_cistatic int csky_mptimer_starting_cpu(unsigned int cpu)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct timer_of *to = per_cpu_ptr(&csky_to, cpu);
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	to->clkevt.cpumask = cpumask_of(cpu);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	enable_percpu_irq(csky_mptimer_irq, 0);
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	clockevents_config_and_register(&to->clkevt, timer_of_rate(to),
8562306a36Sopenharmony_ci					2, ULONG_MAX);
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	return 0;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_cistatic int csky_mptimer_dying_cpu(unsigned int cpu)
9162306a36Sopenharmony_ci{
9262306a36Sopenharmony_ci	disable_percpu_irq(csky_mptimer_irq);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	return 0;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci/*
9862306a36Sopenharmony_ci * clock source
9962306a36Sopenharmony_ci */
10062306a36Sopenharmony_cistatic u64 notrace sched_clock_read(void)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	return (u64)mfcr(PTIM_CCVR);
10362306a36Sopenharmony_ci}
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_cistatic u64 clksrc_read(struct clocksource *c)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	return (u64)mfcr(PTIM_CCVR);
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistruct clocksource csky_clocksource = {
11162306a36Sopenharmony_ci	.name	= "csky",
11262306a36Sopenharmony_ci	.rating	= 400,
11362306a36Sopenharmony_ci	.mask	= CLOCKSOURCE_MASK(32),
11462306a36Sopenharmony_ci	.flags	= CLOCK_SOURCE_IS_CONTINUOUS,
11562306a36Sopenharmony_ci	.read	= clksrc_read,
11662306a36Sopenharmony_ci};
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_cistatic int __init csky_mptimer_init(struct device_node *np)
11962306a36Sopenharmony_ci{
12062306a36Sopenharmony_ci	int ret, cpu, cpu_rollback;
12162306a36Sopenharmony_ci	struct timer_of *to = NULL;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	/*
12462306a36Sopenharmony_ci	 * Csky_mptimer is designed for C-SKY SMP multi-processors and
12562306a36Sopenharmony_ci	 * every core has it's own private irq and regs for clkevt and
12662306a36Sopenharmony_ci	 * clksrc.
12762306a36Sopenharmony_ci	 *
12862306a36Sopenharmony_ci	 * The regs is accessed by cpu instruction: mfcr/mtcr instead of
12962306a36Sopenharmony_ci	 * mmio map style. So we needn't mmio-address in dts, but we still
13062306a36Sopenharmony_ci	 * need to give clk and irq number.
13162306a36Sopenharmony_ci	 *
13262306a36Sopenharmony_ci	 * We use private irq for the mptimer and irq number is the same
13362306a36Sopenharmony_ci	 * for every core. So we use request_percpu_irq() in timer_of_init.
13462306a36Sopenharmony_ci	 */
13562306a36Sopenharmony_ci	csky_mptimer_irq = irq_of_parse_and_map(np, 0);
13662306a36Sopenharmony_ci	if (csky_mptimer_irq <= 0)
13762306a36Sopenharmony_ci		return -EINVAL;
13862306a36Sopenharmony_ci
13962306a36Sopenharmony_ci	ret = request_percpu_irq(csky_mptimer_irq, csky_timer_interrupt,
14062306a36Sopenharmony_ci				 "csky_mp_timer", &csky_to);
14162306a36Sopenharmony_ci	if (ret)
14262306a36Sopenharmony_ci		return -EINVAL;
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci	for_each_possible_cpu(cpu) {
14562306a36Sopenharmony_ci		to = per_cpu_ptr(&csky_to, cpu);
14662306a36Sopenharmony_ci		ret = timer_of_init(np, to);
14762306a36Sopenharmony_ci		if (ret)
14862306a36Sopenharmony_ci			goto rollback;
14962306a36Sopenharmony_ci	}
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	clocksource_register_hz(&csky_clocksource, timer_of_rate(to));
15262306a36Sopenharmony_ci	sched_clock_register(sched_clock_read, 32, timer_of_rate(to));
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci	ret = cpuhp_setup_state(CPUHP_AP_CSKY_TIMER_STARTING,
15562306a36Sopenharmony_ci				"clockevents/csky/timer:starting",
15662306a36Sopenharmony_ci				csky_mptimer_starting_cpu,
15762306a36Sopenharmony_ci				csky_mptimer_dying_cpu);
15862306a36Sopenharmony_ci	if (ret)
15962306a36Sopenharmony_ci		return -EINVAL;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	return 0;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cirollback:
16462306a36Sopenharmony_ci	for_each_possible_cpu(cpu_rollback) {
16562306a36Sopenharmony_ci		if (cpu_rollback == cpu)
16662306a36Sopenharmony_ci			break;
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci		to = per_cpu_ptr(&csky_to, cpu_rollback);
16962306a36Sopenharmony_ci		timer_of_cleanup(to);
17062306a36Sopenharmony_ci	}
17162306a36Sopenharmony_ci	return -EINVAL;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ciTIMER_OF_DECLARE(csky_mptimer, "csky,mptimer", csky_mptimer_init);
174