summaryrefslogtreecommitdiff
path: root/arch/c6x/platforms/timer64.c
blob: 3c73d74a46742a5244a01ac320ac633bad769f50 (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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
/*
 *  Copyright (C) 2010, 2011 Texas Instruments Incorporated
 *  Contributed by: Mark Salter (msalter@redhat.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 */

#include <linux/clockchips.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <asm/soc.h>
#include <asm/dscr.h>
#include <asm/special_insns.h>
#include <asm/timer64.h>

struct timer_regs {
	u32	reserved0;
	u32	emumgt;
	u32	reserved1;
	u32	reserved2;
	u32	cntlo;
	u32	cnthi;
	u32	prdlo;
	u32	prdhi;
	u32	tcr;
	u32	tgcr;
	u32	wdtcr;
};

static struct timer_regs __iomem *timer;

#define TCR_TSTATLO	     0x001
#define TCR_INVOUTPLO	     0x002
#define TCR_INVINPLO	     0x004
#define TCR_CPLO	     0x008
#define TCR_ENAMODELO_ONCE   0x040
#define TCR_ENAMODELO_CONT   0x080
#define TCR_ENAMODELO_MASK   0x0c0
#define TCR_PWIDLO_MASK      0x030
#define TCR_CLKSRCLO	     0x100
#define TCR_TIENLO	     0x200
#define TCR_TSTATHI	     (0x001 << 16)
#define TCR_INVOUTPHI	     (0x002 << 16)
#define TCR_CPHI	     (0x008 << 16)
#define TCR_PWIDHI_MASK      (0x030 << 16)
#define TCR_ENAMODEHI_ONCE   (0x040 << 16)
#define TCR_ENAMODEHI_CONT   (0x080 << 16)
#define TCR_ENAMODEHI_MASK   (0x0c0 << 16)

#define TGCR_TIMLORS	     0x001
#define TGCR_TIMHIRS	     0x002
#define TGCR_TIMMODE_UD32    0x004
#define TGCR_TIMMODE_WDT64   0x008
#define TGCR_TIMMODE_CD32    0x00c
#define TGCR_TIMMODE_MASK    0x00c
#define TGCR_PSCHI_MASK      (0x00f << 8)
#define TGCR_TDDRHI_MASK     (0x00f << 12)

/*
 * Timer clocks are divided down from the CPU clock
 * The divisor is in the EMUMGTCLKSPD register
 */
#define TIMER_DIVISOR \
	((soc_readl(&timer->emumgt) & (0xf << 16)) >> 16)

#define TIMER64_RATE (c6x_core_freq / TIMER_DIVISOR)

#define TIMER64_MODE_DISABLED 0
#define TIMER64_MODE_ONE_SHOT TCR_ENAMODELO_ONCE
#define TIMER64_MODE_PERIODIC TCR_ENAMODELO_CONT

static int timer64_mode;
static int timer64_devstate_id = -1;

static void timer64_config(unsigned long period)
{
	u32 tcr = soc_readl(&timer->tcr) & ~TCR_ENAMODELO_MASK;

	soc_writel(tcr, &timer->tcr);
	soc_writel(period - 1, &timer->prdlo);
	soc_writel(0, &timer->cntlo);
	tcr |= timer64_mode;
	soc_writel(tcr, &timer->tcr);
}

static void timer64_enable(void)
{
	u32 val;

	if (timer64_devstate_id >= 0)
		dscr_set_devstate(timer64_devstate_id, DSCR_DEVSTATE_ENABLED);

	/* disable timer, reset count */
	soc_writel(soc_readl(&timer->tcr) & ~TCR_ENAMODELO_MASK, &timer->tcr);
	soc_writel(0, &timer->prdlo);

	/* use internal clock and 1 cycle pulse width */
	val = soc_readl(&timer->tcr);
	soc_writel(val & ~(TCR_CLKSRCLO | TCR_PWIDLO_MASK), &timer->tcr);

	/* dual 32-bit unchained mode */
	val = soc_readl(&timer->tgcr) & ~TGCR_TIMMODE_MASK;
	soc_writel(val, &timer->tgcr);
	soc_writel(val | (TGCR_TIMLORS | TGCR_TIMMODE_UD32), &timer->tgcr);
}

static void timer64_disable(void)
{
	/* disable timer, reset count */
	soc_writel(soc_readl(&timer->tcr) & ~TCR_ENAMODELO_MASK, &timer->tcr);
	soc_writel(0, &timer->prdlo);

	if (timer64_devstate_id >= 0)
		dscr_set_devstate(timer64_devstate_id, DSCR_DEVSTATE_DISABLED);
}

static int next_event(unsigned long delta,
		      struct clock_event_device *evt)
{
	timer64_config(delta);
	return 0;
}

static void set_clock_mode(enum clock_event_mode mode,
			   struct clock_event_device *evt)
{
	switch (mode) {
	case CLOCK_EVT_MODE_PERIODIC:
		timer64_enable();
		timer64_mode = TIMER64_MODE_PERIODIC;
		timer64_config(TIMER64_RATE / HZ);
		break;
	case CLOCK_EVT_MODE_ONESHOT:
		timer64_enable();
		timer64_mode = TIMER64_MODE_ONE_SHOT;
		break;
	case CLOCK_EVT_MODE_UNUSED:
	case CLOCK_EVT_MODE_SHUTDOWN:
		timer64_mode = TIMER64_MODE_DISABLED;
		timer64_disable();
		break;
	case CLOCK_EVT_MODE_RESUME:
		break;
	}
}

static struct clock_event_device t64_clockevent_device = {
	.name		= "TIMER64_EVT32_TIMER",
	.features	= CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC,
	.rating		= 200,
	.set_mode	= set_clock_mode,
	.set_next_event	= next_event,
};

static irqreturn_t timer_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *cd = &t64_clockevent_device;

	cd->event_handler(cd);

	return IRQ_HANDLED;
}

static struct irqaction timer_iact = {
	.name		= "timer",
	.flags		= IRQF_TIMER,
	.handler	= timer_interrupt,
	.dev_id		= &t64_clockevent_device,
};

void __init timer64_init(void)
{
	struct clock_event_device *cd = &t64_clockevent_device;
	struct device_node *np, *first = NULL;
	u32 val;
	int err, found = 0;

	for_each_compatible_node(np, NULL, "ti,c64x+timer64") {
		err = of_property_read_u32(np, "ti,core-mask", &val);
		if (!err) {
			if (val & (1 << get_coreid())) {
				found = 1;
				break;
			}
		} else if (!first)
			first = np;
	}
	if (!found) {
		/* try first one with no core-mask */
		if (first)
			np = of_node_get(first);
		else {
			pr_debug("Cannot find ti,c64x+timer64 timer.\n");
			return;
		}
	}

	timer = of_iomap(np, 0);
	if (!timer) {
		pr_debug("%s: Cannot map timer registers.\n", np->full_name);
		goto out;
	}
	pr_debug("%s: Timer registers=%p.\n", np->full_name, timer);

	cd->irq	= irq_of_parse_and_map(np, 0);
	if (cd->irq == NO_IRQ) {
		pr_debug("%s: Cannot find interrupt.\n", np->full_name);
		iounmap(timer);
		goto out;
	}

	/* If there is a device state control, save the ID. */
	err = of_property_read_u32(np, "ti,dscr-dev-enable", &val);
	if (!err) {
		timer64_devstate_id = val;

		/*
		 * It is necessary to enable the timer block here because
		 * the TIMER_DIVISOR macro needs to read a timer register
		 * to get the divisor.
		 */
		dscr_set_devstate(timer64_devstate_id, DSCR_DEVSTATE_ENABLED);
	}

	pr_debug("%s: Timer irq=%d.\n", np->full_name, cd->irq);

	clockevents_calc_mult_shift(cd, c6x_core_freq / TIMER_DIVISOR, 5);

	cd->max_delta_ns	= clockevent_delta2ns(0x7fffffff, cd);
	cd->min_delta_ns	= clockevent_delta2ns(250, cd);

	cd->cpumask		= cpumask_of(smp_processor_id());

	clockevents_register_device(cd);
	setup_irq(cd->irq, &timer_iact);

out:
	of_node_put(np);
	return;
}