summaryrefslogtreecommitdiffstats
path: root/drivers/clocksource/timer-gx6605s.c
blob: 80d0939d040b515c043723b150b92f3360900cf0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.

#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched_clock.h>

#include "timer-of.h"

#define CLKSRC_OFFSET	0x40

#define TIMER_STATUS	0x00
#define TIMER_VALUE	0x04
#define TIMER_CONTRL	0x10
#define TIMER_CONFIG	0x20
#define TIMER_DIV	0x24
#define TIMER_INI	0x28

#define GX6605S_STATUS_CLR	BIT(0)
#define GX6605S_CONTRL_RST	BIT(0)
#define GX6605S_CONTRL_START	BIT(1)
#define GX6605S_CONFIG_EN	BIT(0)
#define GX6605S_CONFIG_IRQ_EN	BIT(1)

static irqreturn_t gx6605s_timer_interrupt(int irq, void *dev)
{
	struct clock_event_device *ce = dev;
	void __iomem *base = timer_of_base(to_timer_of(ce));

	writel_relaxed(GX6605S_STATUS_CLR, base + TIMER_STATUS);

	ce->event_handler(ce);

	return IRQ_HANDLED;
}

static int gx6605s_timer_set_oneshot(struct clock_event_device *ce)
{
	void __iomem *base = timer_of_base(to_timer_of(ce));

	/* reset and stop counter */
	writel_relaxed(GX6605S_CONTRL_RST, base + TIMER_CONTRL);

	/* enable with irq and start */
	writel_relaxed(GX6605S_CONFIG_EN | GX6605S_CONFIG_IRQ_EN,
		       base + TIMER_CONFIG);

	return 0;
}

static int gx6605s_timer_set_next_event(unsigned long delta,
					struct clock_event_device *ce)
{
	void __iomem *base = timer_of_base(to_timer_of(ce));

	/* use reset to pause timer */
	writel_relaxed(GX6605S_CONTRL_RST, base + TIMER_CONTRL);

	/* config next timeout value */
	writel_relaxed(ULONG_MAX - delta, base + TIMER_INI);
	writel_relaxed(GX6605S_CONTRL_START, base + TIMER_CONTRL);

	return 0;
}

static int gx6605s_timer_shutdown(struct clock_event_device *ce)
{
	void __iomem *base = timer_of_base(to_timer_of(ce));

	writel_relaxed(0, base + TIMER_CONTRL);
	writel_relaxed(0, base + TIMER_CONFIG);

	return 0;
}

static struct timer_of to = {
	.flags = TIMER_OF_IRQ | TIMER_OF_BASE | TIMER_OF_CLOCK,
	.clkevt = {
		.rating			= 300,
		.features		= CLOCK_EVT_FEAT_DYNIRQ |
					  CLOCK_EVT_FEAT_ONESHOT,
		.set_state_shutdown	= gx6605s_timer_shutdown,
		.set_state_oneshot	= gx6605s_timer_set_oneshot,
		.set_next_event		= gx6605s_timer_set_next_event,
		.cpumask		= cpu_possible_mask,
	},
	.of_irq = {
		.handler		= gx6605s_timer_interrupt,
		.flags			= IRQF_TIMER | IRQF_IRQPOLL,
	},
};

static u64 notrace gx6605s_sched_clock_read(void)
{
	void __iomem *base;

	base = timer_of_base(&to) + CLKSRC_OFFSET;

	return (u64)readl_relaxed(base + TIMER_VALUE);
}

static void gx6605s_clkevt_init(void __iomem *base)
{
	writel_relaxed(0, base + TIMER_DIV);
	writel_relaxed(0, base + TIMER_CONFIG);

	clockevents_config_and_register(&to.clkevt, timer_of_rate(&to), 2,
					ULONG_MAX);
}

static int gx6605s_clksrc_init(void __iomem *base)
{
	writel_relaxed(0, base + TIMER_DIV);
	writel_relaxed(0, base + TIMER_INI);

	writel_relaxed(GX6605S_CONTRL_RST, base + TIMER_CONTRL);

	writel_relaxed(GX6605S_CONFIG_EN, base + TIMER_CONFIG);

	writel_relaxed(GX6605S_CONTRL_START, base + TIMER_CONTRL);

	sched_clock_register(gx6605s_sched_clock_read, 32, timer_of_rate(&to));

	return clocksource_mmio_init(base + TIMER_VALUE, "gx6605s",
			timer_of_rate(&to), 200, 32, clocksource_mmio_readl_up);
}

static int __init gx6605s_timer_init(struct device_node *np)
{
	int ret;

	/*
	 * The timer driver is for nationalchip gx6605s SOC and there are two
	 * same timer in gx6605s. We use one for clkevt and another for clksrc.
	 *
	 * The timer is mmio map to access, so we need give mmio address in dts.
	 *
	 * It provides a 32bit countup timer and interrupt will be caused by
	 * count-overflow.
	 * So we need set-next-event by ULONG_MAX - delta in TIMER_INI reg.
	 *
	 * The counter at 0x0  offset is clock event.
	 * The counter at 0x40 offset is clock source.
	 * They are the same in hardware, just different used by driver.
	 */
	ret = timer_of_init(np, &to);
	if (ret)
		return ret;

	gx6605s_clkevt_init(timer_of_base(&to));

	return gx6605s_clksrc_init(timer_of_base(&to) + CLKSRC_OFFSET);
}
TIMER_OF_DECLARE(csky_gx6605s_timer, "csky,gx6605s-timer", gx6605s_timer_init);
OpenPOWER on IntegriCloud