18c2ecf20Sopenharmony_ci/*
28c2ecf20Sopenharmony_ci * Copyright (C) 2012 Broadcom Corporation
38c2ecf20Sopenharmony_ci *
48c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or
58c2ecf20Sopenharmony_ci * modify it under the terms of the GNU General Public License as
68c2ecf20Sopenharmony_ci * published by the Free Software Foundation version 2.
78c2ecf20Sopenharmony_ci *
88c2ecf20Sopenharmony_ci * This program is distributed "as is" WITHOUT ANY WARRANTY of any
98c2ecf20Sopenharmony_ci * kind, whether express or implied; without even the implied warranty
108c2ecf20Sopenharmony_ci * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
118c2ecf20Sopenharmony_ci * GNU General Public License for more details.
128c2ecf20Sopenharmony_ci */
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_ci#include <linux/init.h>
158c2ecf20Sopenharmony_ci#include <linux/irq.h>
168c2ecf20Sopenharmony_ci#include <linux/interrupt.h>
178c2ecf20Sopenharmony_ci#include <linux/jiffies.h>
188c2ecf20Sopenharmony_ci#include <linux/clockchips.h>
198c2ecf20Sopenharmony_ci#include <linux/types.h>
208c2ecf20Sopenharmony_ci#include <linux/clk.h>
218c2ecf20Sopenharmony_ci
228c2ecf20Sopenharmony_ci#include <linux/io.h>
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci#include <linux/of.h>
258c2ecf20Sopenharmony_ci#include <linux/of_address.h>
268c2ecf20Sopenharmony_ci#include <linux/of_irq.h>
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define KONA_GPTIMER_STCS_OFFSET			0x00000000
308c2ecf20Sopenharmony_ci#define KONA_GPTIMER_STCLO_OFFSET			0x00000004
318c2ecf20Sopenharmony_ci#define KONA_GPTIMER_STCHI_OFFSET			0x00000008
328c2ecf20Sopenharmony_ci#define KONA_GPTIMER_STCM0_OFFSET			0x0000000C
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci#define KONA_GPTIMER_STCS_TIMER_MATCH_SHIFT		0
358c2ecf20Sopenharmony_ci#define KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT		4
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_cistruct kona_bcm_timers {
388c2ecf20Sopenharmony_ci	int tmr_irq;
398c2ecf20Sopenharmony_ci	void __iomem *tmr_regs;
408c2ecf20Sopenharmony_ci};
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_cistatic struct kona_bcm_timers timers;
438c2ecf20Sopenharmony_ci
448c2ecf20Sopenharmony_cistatic u32 arch_timer_rate;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci/*
478c2ecf20Sopenharmony_ci * We use the peripheral timers for system tick, the cpu global timer for
488c2ecf20Sopenharmony_ci * profile tick
498c2ecf20Sopenharmony_ci */
508c2ecf20Sopenharmony_cistatic void kona_timer_disable_and_clear(void __iomem *base)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	uint32_t reg;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	/*
558c2ecf20Sopenharmony_ci	 * clear and disable interrupts
568c2ecf20Sopenharmony_ci	 * We are using compare/match register 0 for our system interrupts
578c2ecf20Sopenharmony_ci	 */
588c2ecf20Sopenharmony_ci	reg = readl(base + KONA_GPTIMER_STCS_OFFSET);
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_ci	/* Clear compare (0) interrupt */
618c2ecf20Sopenharmony_ci	reg |= 1 << KONA_GPTIMER_STCS_TIMER_MATCH_SHIFT;
628c2ecf20Sopenharmony_ci	/* disable compare */
638c2ecf20Sopenharmony_ci	reg &= ~(1 << KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT);
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	writel(reg, base + KONA_GPTIMER_STCS_OFFSET);
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_cistatic int
708c2ecf20Sopenharmony_cikona_timer_get_counter(void __iomem *timer_base, uint32_t *msw, uint32_t *lsw)
718c2ecf20Sopenharmony_ci{
728c2ecf20Sopenharmony_ci	int loop_limit = 3;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci	/*
758c2ecf20Sopenharmony_ci	 * Read 64-bit free running counter
768c2ecf20Sopenharmony_ci	 * 1. Read hi-word
778c2ecf20Sopenharmony_ci	 * 2. Read low-word
788c2ecf20Sopenharmony_ci	 * 3. Read hi-word again
798c2ecf20Sopenharmony_ci	 * 4.1
808c2ecf20Sopenharmony_ci	 *      if new hi-word is not equal to previously read hi-word, then
818c2ecf20Sopenharmony_ci	 *      start from #1
828c2ecf20Sopenharmony_ci	 * 4.2
838c2ecf20Sopenharmony_ci	 *      if new hi-word is equal to previously read hi-word then stop.
848c2ecf20Sopenharmony_ci	 */
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_ci	do {
878c2ecf20Sopenharmony_ci		*msw = readl(timer_base + KONA_GPTIMER_STCHI_OFFSET);
888c2ecf20Sopenharmony_ci		*lsw = readl(timer_base + KONA_GPTIMER_STCLO_OFFSET);
898c2ecf20Sopenharmony_ci		if (*msw == readl(timer_base + KONA_GPTIMER_STCHI_OFFSET))
908c2ecf20Sopenharmony_ci			break;
918c2ecf20Sopenharmony_ci	} while (--loop_limit);
928c2ecf20Sopenharmony_ci	if (!loop_limit) {
938c2ecf20Sopenharmony_ci		pr_err("bcm_kona_timer: getting counter failed.\n");
948c2ecf20Sopenharmony_ci		pr_err(" Timer will be impacted\n");
958c2ecf20Sopenharmony_ci		return -ETIMEDOUT;
968c2ecf20Sopenharmony_ci	}
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_ci	return 0;
998c2ecf20Sopenharmony_ci}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_cistatic int kona_timer_set_next_event(unsigned long clc,
1028c2ecf20Sopenharmony_ci				  struct clock_event_device *unused)
1038c2ecf20Sopenharmony_ci{
1048c2ecf20Sopenharmony_ci	/*
1058c2ecf20Sopenharmony_ci	 * timer (0) is disabled by the timer interrupt already
1068c2ecf20Sopenharmony_ci	 * so, here we reload the next event value and re-enable
1078c2ecf20Sopenharmony_ci	 * the timer.
1088c2ecf20Sopenharmony_ci	 *
1098c2ecf20Sopenharmony_ci	 * This way, we are potentially losing the time between
1108c2ecf20Sopenharmony_ci	 * timer-interrupt->set_next_event. CPU local timers, when
1118c2ecf20Sopenharmony_ci	 * they come in should get rid of skew.
1128c2ecf20Sopenharmony_ci	 */
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	uint32_t lsw, msw;
1158c2ecf20Sopenharmony_ci	uint32_t reg;
1168c2ecf20Sopenharmony_ci	int ret;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	ret = kona_timer_get_counter(timers.tmr_regs, &msw, &lsw);
1198c2ecf20Sopenharmony_ci	if (ret)
1208c2ecf20Sopenharmony_ci		return ret;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	/* Load the "next" event tick value */
1238c2ecf20Sopenharmony_ci	writel(lsw + clc, timers.tmr_regs + KONA_GPTIMER_STCM0_OFFSET);
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci	/* Enable compare */
1268c2ecf20Sopenharmony_ci	reg = readl(timers.tmr_regs + KONA_GPTIMER_STCS_OFFSET);
1278c2ecf20Sopenharmony_ci	reg |= (1 << KONA_GPTIMER_STCS_COMPARE_ENABLE_SHIFT);
1288c2ecf20Sopenharmony_ci	writel(reg, timers.tmr_regs + KONA_GPTIMER_STCS_OFFSET);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	return 0;
1318c2ecf20Sopenharmony_ci}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cistatic int kona_timer_shutdown(struct clock_event_device *evt)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	kona_timer_disable_and_clear(timers.tmr_regs);
1368c2ecf20Sopenharmony_ci	return 0;
1378c2ecf20Sopenharmony_ci}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_cistatic struct clock_event_device kona_clockevent_timer = {
1408c2ecf20Sopenharmony_ci	.name = "timer 1",
1418c2ecf20Sopenharmony_ci	.features = CLOCK_EVT_FEAT_ONESHOT,
1428c2ecf20Sopenharmony_ci	.set_next_event = kona_timer_set_next_event,
1438c2ecf20Sopenharmony_ci	.set_state_shutdown = kona_timer_shutdown,
1448c2ecf20Sopenharmony_ci	.tick_resume = kona_timer_shutdown,
1458c2ecf20Sopenharmony_ci};
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_cistatic void __init kona_timer_clockevents_init(void)
1488c2ecf20Sopenharmony_ci{
1498c2ecf20Sopenharmony_ci	kona_clockevent_timer.cpumask = cpumask_of(0);
1508c2ecf20Sopenharmony_ci	clockevents_config_and_register(&kona_clockevent_timer,
1518c2ecf20Sopenharmony_ci		arch_timer_rate, 6, 0xffffffff);
1528c2ecf20Sopenharmony_ci}
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_cistatic irqreturn_t kona_timer_interrupt(int irq, void *dev_id)
1558c2ecf20Sopenharmony_ci{
1568c2ecf20Sopenharmony_ci	struct clock_event_device *evt = &kona_clockevent_timer;
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_ci	kona_timer_disable_and_clear(timers.tmr_regs);
1598c2ecf20Sopenharmony_ci	evt->event_handler(evt);
1608c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
1618c2ecf20Sopenharmony_ci}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cistatic int __init kona_timer_init(struct device_node *node)
1648c2ecf20Sopenharmony_ci{
1658c2ecf20Sopenharmony_ci	u32 freq;
1668c2ecf20Sopenharmony_ci	struct clk *external_clk;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	external_clk = of_clk_get_by_name(node, NULL);
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	if (!IS_ERR(external_clk)) {
1718c2ecf20Sopenharmony_ci		arch_timer_rate = clk_get_rate(external_clk);
1728c2ecf20Sopenharmony_ci		clk_prepare_enable(external_clk);
1738c2ecf20Sopenharmony_ci	} else if (!of_property_read_u32(node, "clock-frequency", &freq)) {
1748c2ecf20Sopenharmony_ci		arch_timer_rate = freq;
1758c2ecf20Sopenharmony_ci	} else {
1768c2ecf20Sopenharmony_ci		pr_err("Kona Timer v1 unable to determine clock-frequency\n");
1778c2ecf20Sopenharmony_ci		return -EINVAL;
1788c2ecf20Sopenharmony_ci	}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_ci	/* Setup IRQ numbers */
1818c2ecf20Sopenharmony_ci	timers.tmr_irq = irq_of_parse_and_map(node, 0);
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	/* Setup IO addresses */
1848c2ecf20Sopenharmony_ci	timers.tmr_regs = of_iomap(node, 0);
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	kona_timer_disable_and_clear(timers.tmr_regs);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	kona_timer_clockevents_init();
1898c2ecf20Sopenharmony_ci	if (request_irq(timers.tmr_irq, kona_timer_interrupt, IRQF_TIMER,
1908c2ecf20Sopenharmony_ci			"Kona Timer Tick", NULL))
1918c2ecf20Sopenharmony_ci		pr_err("%s: request_irq() failed\n", "Kona Timer Tick");
1928c2ecf20Sopenharmony_ci	kona_timer_set_next_event((arch_timer_rate / HZ), NULL);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci	return 0;
1958c2ecf20Sopenharmony_ci}
1968c2ecf20Sopenharmony_ci
1978c2ecf20Sopenharmony_ciTIMER_OF_DECLARE(brcm_kona, "brcm,kona-timer", kona_timer_init);
1988c2ecf20Sopenharmony_ci/*
1998c2ecf20Sopenharmony_ci * bcm,kona-timer is deprecated by brcm,kona-timer
2008c2ecf20Sopenharmony_ci * being kept here for driver compatibility
2018c2ecf20Sopenharmony_ci */
2028c2ecf20Sopenharmony_ciTIMER_OF_DECLARE(bcm_kona, "bcm,kona-timer", kona_timer_init);
203