18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci// Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.
38c2ecf20Sopenharmony_ci
48c2ecf20Sopenharmony_ci#include <linux/init.h>
58c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
68c2ecf20Sopenharmony_ci#include <linux/sched_clock.h>
78c2ecf20Sopenharmony_ci#include <linux/cpu.h>
88c2ecf20Sopenharmony_ci#include <linux/of_irq.h>
98c2ecf20Sopenharmony_ci#include <asm/reg_ops.h>
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include "timer-of.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#define PTIM_CCVR	"cr<3, 14>"
148c2ecf20Sopenharmony_ci#define PTIM_CTLR	"cr<0, 14>"
158c2ecf20Sopenharmony_ci#define PTIM_LVR	"cr<6, 14>"
168c2ecf20Sopenharmony_ci#define PTIM_TSR	"cr<1, 14>"
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_cistatic int csky_mptimer_irq;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_cistatic int csky_mptimer_set_next_event(unsigned long delta,
218c2ecf20Sopenharmony_ci				       struct clock_event_device *ce)
228c2ecf20Sopenharmony_ci{
238c2ecf20Sopenharmony_ci	mtcr(PTIM_LVR, delta);
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci	return 0;
268c2ecf20Sopenharmony_ci}
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic int csky_mptimer_shutdown(struct clock_event_device *ce)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	mtcr(PTIM_CTLR, 0);
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci	return 0;
338c2ecf20Sopenharmony_ci}
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_cistatic int csky_mptimer_oneshot(struct clock_event_device *ce)
368c2ecf20Sopenharmony_ci{
378c2ecf20Sopenharmony_ci	mtcr(PTIM_CTLR, 1);
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_ci	return 0;
408c2ecf20Sopenharmony_ci}
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic int csky_mptimer_oneshot_stopped(struct clock_event_device *ce)
438c2ecf20Sopenharmony_ci{
448c2ecf20Sopenharmony_ci	mtcr(PTIM_CTLR, 0);
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	return 0;
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_cistatic DEFINE_PER_CPU(struct timer_of, csky_to) = {
508c2ecf20Sopenharmony_ci	.flags					= TIMER_OF_CLOCK,
518c2ecf20Sopenharmony_ci	.clkevt = {
528c2ecf20Sopenharmony_ci		.rating				= 300,
538c2ecf20Sopenharmony_ci		.features			= CLOCK_EVT_FEAT_PERCPU |
548c2ecf20Sopenharmony_ci						  CLOCK_EVT_FEAT_ONESHOT,
558c2ecf20Sopenharmony_ci		.set_state_shutdown		= csky_mptimer_shutdown,
568c2ecf20Sopenharmony_ci		.set_state_oneshot		= csky_mptimer_oneshot,
578c2ecf20Sopenharmony_ci		.set_state_oneshot_stopped	= csky_mptimer_oneshot_stopped,
588c2ecf20Sopenharmony_ci		.set_next_event			= csky_mptimer_set_next_event,
598c2ecf20Sopenharmony_ci	},
608c2ecf20Sopenharmony_ci};
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_cistatic irqreturn_t csky_timer_interrupt(int irq, void *dev)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	struct timer_of *to = this_cpu_ptr(&csky_to);
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci	mtcr(PTIM_TSR, 0);
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci	to->clkevt.event_handler(&to->clkevt);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci/*
748c2ecf20Sopenharmony_ci * clock event for percpu
758c2ecf20Sopenharmony_ci */
768c2ecf20Sopenharmony_cistatic int csky_mptimer_starting_cpu(unsigned int cpu)
778c2ecf20Sopenharmony_ci{
788c2ecf20Sopenharmony_ci	struct timer_of *to = per_cpu_ptr(&csky_to, cpu);
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci	to->clkevt.cpumask = cpumask_of(cpu);
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	enable_percpu_irq(csky_mptimer_irq, 0);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	clockevents_config_and_register(&to->clkevt, timer_of_rate(to),
858c2ecf20Sopenharmony_ci					2, ULONG_MAX);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	return 0;
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic int csky_mptimer_dying_cpu(unsigned int cpu)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	disable_percpu_irq(csky_mptimer_irq);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	return 0;
958c2ecf20Sopenharmony_ci}
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci/*
988c2ecf20Sopenharmony_ci * clock source
998c2ecf20Sopenharmony_ci */
1008c2ecf20Sopenharmony_cistatic u64 notrace sched_clock_read(void)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	return (u64)mfcr(PTIM_CCVR);
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ci
1058c2ecf20Sopenharmony_cistatic u64 clksrc_read(struct clocksource *c)
1068c2ecf20Sopenharmony_ci{
1078c2ecf20Sopenharmony_ci	return (u64)mfcr(PTIM_CCVR);
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistruct clocksource csky_clocksource = {
1118c2ecf20Sopenharmony_ci	.name	= "csky",
1128c2ecf20Sopenharmony_ci	.rating	= 400,
1138c2ecf20Sopenharmony_ci	.mask	= CLOCKSOURCE_MASK(32),
1148c2ecf20Sopenharmony_ci	.flags	= CLOCK_SOURCE_IS_CONTINUOUS,
1158c2ecf20Sopenharmony_ci	.read	= clksrc_read,
1168c2ecf20Sopenharmony_ci};
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_cistatic int __init csky_mptimer_init(struct device_node *np)
1198c2ecf20Sopenharmony_ci{
1208c2ecf20Sopenharmony_ci	int ret, cpu, cpu_rollback;
1218c2ecf20Sopenharmony_ci	struct timer_of *to = NULL;
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	/*
1248c2ecf20Sopenharmony_ci	 * Csky_mptimer is designed for C-SKY SMP multi-processors and
1258c2ecf20Sopenharmony_ci	 * every core has it's own private irq and regs for clkevt and
1268c2ecf20Sopenharmony_ci	 * clksrc.
1278c2ecf20Sopenharmony_ci	 *
1288c2ecf20Sopenharmony_ci	 * The regs is accessed by cpu instruction: mfcr/mtcr instead of
1298c2ecf20Sopenharmony_ci	 * mmio map style. So we needn't mmio-address in dts, but we still
1308c2ecf20Sopenharmony_ci	 * need to give clk and irq number.
1318c2ecf20Sopenharmony_ci	 *
1328c2ecf20Sopenharmony_ci	 * We use private irq for the mptimer and irq number is the same
1338c2ecf20Sopenharmony_ci	 * for every core. So we use request_percpu_irq() in timer_of_init.
1348c2ecf20Sopenharmony_ci	 */
1358c2ecf20Sopenharmony_ci	csky_mptimer_irq = irq_of_parse_and_map(np, 0);
1368c2ecf20Sopenharmony_ci	if (csky_mptimer_irq <= 0)
1378c2ecf20Sopenharmony_ci		return -EINVAL;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	ret = request_percpu_irq(csky_mptimer_irq, csky_timer_interrupt,
1408c2ecf20Sopenharmony_ci				 "csky_mp_timer", &csky_to);
1418c2ecf20Sopenharmony_ci	if (ret)
1428c2ecf20Sopenharmony_ci		return -EINVAL;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci	for_each_possible_cpu(cpu) {
1458c2ecf20Sopenharmony_ci		to = per_cpu_ptr(&csky_to, cpu);
1468c2ecf20Sopenharmony_ci		ret = timer_of_init(np, to);
1478c2ecf20Sopenharmony_ci		if (ret)
1488c2ecf20Sopenharmony_ci			goto rollback;
1498c2ecf20Sopenharmony_ci	}
1508c2ecf20Sopenharmony_ci
1518c2ecf20Sopenharmony_ci	clocksource_register_hz(&csky_clocksource, timer_of_rate(to));
1528c2ecf20Sopenharmony_ci	sched_clock_register(sched_clock_read, 32, timer_of_rate(to));
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	ret = cpuhp_setup_state(CPUHP_AP_CSKY_TIMER_STARTING,
1558c2ecf20Sopenharmony_ci				"clockevents/csky/timer:starting",
1568c2ecf20Sopenharmony_ci				csky_mptimer_starting_cpu,
1578c2ecf20Sopenharmony_ci				csky_mptimer_dying_cpu);
1588c2ecf20Sopenharmony_ci	if (ret)
1598c2ecf20Sopenharmony_ci		return -EINVAL;
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	return 0;
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cirollback:
1648c2ecf20Sopenharmony_ci	for_each_possible_cpu(cpu_rollback) {
1658c2ecf20Sopenharmony_ci		if (cpu_rollback == cpu)
1668c2ecf20Sopenharmony_ci			break;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci		to = per_cpu_ptr(&csky_to, cpu_rollback);
1698c2ecf20Sopenharmony_ci		timer_of_cleanup(to);
1708c2ecf20Sopenharmony_ci	}
1718c2ecf20Sopenharmony_ci	return -EINVAL;
1728c2ecf20Sopenharmony_ci}
1738c2ecf20Sopenharmony_ciTIMER_OF_DECLARE(csky_mptimer, "csky,mptimer", csky_mptimer_init);
174