1/*
2 * This file is subject to the terms and conditions of the GNU General Public
3 * License.  See the file "COPYING" in the main directory of this archive
4 * for more details.
5 *
6 * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
7 */
8
9#include <linux/kernel.h>
10#include <linux/err.h>
11#include <linux/init.h>
12#include <linux/export.h>
13#include <linux/spinlock.h>
14#include <linux/interrupt.h>
15#include <linux/clk.h>
16#include <bcm63xx_cpu.h>
17#include <bcm63xx_io.h>
18#include <bcm63xx_timer.h>
19#include <bcm63xx_regs.h>
20
21static DEFINE_RAW_SPINLOCK(timer_reg_lock);
22static DEFINE_RAW_SPINLOCK(timer_data_lock);
23static struct clk *periph_clk;
24
25static struct timer_data {
26	void	(*cb)(void *);
27	void	*data;
28} timer_data[BCM63XX_TIMER_COUNT];
29
30static irqreturn_t timer_interrupt(int irq, void *dev_id)
31{
32	u32 stat;
33	int i;
34
35	raw_spin_lock(&timer_reg_lock);
36	stat = bcm_timer_readl(TIMER_IRQSTAT_REG);
37	bcm_timer_writel(stat, TIMER_IRQSTAT_REG);
38	raw_spin_unlock(&timer_reg_lock);
39
40	for (i = 0; i < BCM63XX_TIMER_COUNT; i++) {
41		if (!(stat & TIMER_IRQSTAT_TIMER_CAUSE(i)))
42			continue;
43
44		raw_spin_lock(&timer_data_lock);
45		if (!timer_data[i].cb) {
46			raw_spin_unlock(&timer_data_lock);
47			continue;
48		}
49
50		timer_data[i].cb(timer_data[i].data);
51		raw_spin_unlock(&timer_data_lock);
52	}
53
54	return IRQ_HANDLED;
55}
56
57int bcm63xx_timer_enable(int id)
58{
59	u32 reg;
60	unsigned long flags;
61
62	if (id >= BCM63XX_TIMER_COUNT)
63		return -EINVAL;
64
65	raw_spin_lock_irqsave(&timer_reg_lock, flags);
66
67	reg = bcm_timer_readl(TIMER_CTLx_REG(id));
68	reg |= TIMER_CTL_ENABLE_MASK;
69	bcm_timer_writel(reg, TIMER_CTLx_REG(id));
70
71	reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
72	reg |= TIMER_IRQSTAT_TIMER_IR_EN(id);
73	bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
74
75	raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
76	return 0;
77}
78
79EXPORT_SYMBOL(bcm63xx_timer_enable);
80
81int bcm63xx_timer_disable(int id)
82{
83	u32 reg;
84	unsigned long flags;
85
86	if (id >= BCM63XX_TIMER_COUNT)
87		return -EINVAL;
88
89	raw_spin_lock_irqsave(&timer_reg_lock, flags);
90
91	reg = bcm_timer_readl(TIMER_CTLx_REG(id));
92	reg &= ~TIMER_CTL_ENABLE_MASK;
93	bcm_timer_writel(reg, TIMER_CTLx_REG(id));
94
95	reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
96	reg &= ~TIMER_IRQSTAT_TIMER_IR_EN(id);
97	bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
98
99	raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
100	return 0;
101}
102
103EXPORT_SYMBOL(bcm63xx_timer_disable);
104
105int bcm63xx_timer_register(int id, void (*callback)(void *data), void *data)
106{
107	unsigned long flags;
108	int ret;
109
110	if (id >= BCM63XX_TIMER_COUNT || !callback)
111		return -EINVAL;
112
113	ret = 0;
114	raw_spin_lock_irqsave(&timer_data_lock, flags);
115	if (timer_data[id].cb) {
116		ret = -EBUSY;
117		goto out;
118	}
119
120	timer_data[id].cb = callback;
121	timer_data[id].data = data;
122
123out:
124	raw_spin_unlock_irqrestore(&timer_data_lock, flags);
125	return ret;
126}
127
128EXPORT_SYMBOL(bcm63xx_timer_register);
129
130void bcm63xx_timer_unregister(int id)
131{
132	unsigned long flags;
133
134	if (id >= BCM63XX_TIMER_COUNT)
135		return;
136
137	raw_spin_lock_irqsave(&timer_data_lock, flags);
138	timer_data[id].cb = NULL;
139	raw_spin_unlock_irqrestore(&timer_data_lock, flags);
140}
141
142EXPORT_SYMBOL(bcm63xx_timer_unregister);
143
144unsigned int bcm63xx_timer_countdown(unsigned int countdown_us)
145{
146	return (clk_get_rate(periph_clk) / (1000 * 1000)) * countdown_us;
147}
148
149EXPORT_SYMBOL(bcm63xx_timer_countdown);
150
151int bcm63xx_timer_set(int id, int monotonic, unsigned int countdown_us)
152{
153	u32 reg, countdown;
154	unsigned long flags;
155
156	if (id >= BCM63XX_TIMER_COUNT)
157		return -EINVAL;
158
159	countdown = bcm63xx_timer_countdown(countdown_us);
160	if (countdown & ~TIMER_CTL_COUNTDOWN_MASK)
161		return -EINVAL;
162
163	raw_spin_lock_irqsave(&timer_reg_lock, flags);
164	reg = bcm_timer_readl(TIMER_CTLx_REG(id));
165
166	if (monotonic)
167		reg &= ~TIMER_CTL_MONOTONIC_MASK;
168	else
169		reg |= TIMER_CTL_MONOTONIC_MASK;
170
171	reg &= ~TIMER_CTL_COUNTDOWN_MASK;
172	reg |= countdown;
173	bcm_timer_writel(reg, TIMER_CTLx_REG(id));
174
175	raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
176	return 0;
177}
178
179EXPORT_SYMBOL(bcm63xx_timer_set);
180
181static int bcm63xx_timer_init(void)
182{
183	int ret, irq;
184	u32 reg;
185
186	reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
187	reg &= ~TIMER_IRQSTAT_TIMER0_IR_EN;
188	reg &= ~TIMER_IRQSTAT_TIMER1_IR_EN;
189	reg &= ~TIMER_IRQSTAT_TIMER2_IR_EN;
190	bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
191
192	periph_clk = clk_get(NULL, "periph");
193	if (IS_ERR(periph_clk))
194		return -ENODEV;
195
196	irq = bcm63xx_get_irq_number(IRQ_TIMER);
197	ret = request_irq(irq, timer_interrupt, 0, "bcm63xx_timer", NULL);
198	if (ret) {
199		pr_err("%s: failed to register irq\n", __func__);
200		return ret;
201	}
202
203	return 0;
204}
205
206arch_initcall(bcm63xx_timer_init);
207