162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
362306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
462306a36Sopenharmony_ci * for more details.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2008 Maxime Bizon <mbizon@freebox.fr>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/kernel.h>
1062306a36Sopenharmony_ci#include <linux/err.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/export.h>
1362306a36Sopenharmony_ci#include <linux/spinlock.h>
1462306a36Sopenharmony_ci#include <linux/interrupt.h>
1562306a36Sopenharmony_ci#include <linux/clk.h>
1662306a36Sopenharmony_ci#include <bcm63xx_cpu.h>
1762306a36Sopenharmony_ci#include <bcm63xx_io.h>
1862306a36Sopenharmony_ci#include <bcm63xx_timer.h>
1962306a36Sopenharmony_ci#include <bcm63xx_regs.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic DEFINE_RAW_SPINLOCK(timer_reg_lock);
2262306a36Sopenharmony_cistatic DEFINE_RAW_SPINLOCK(timer_data_lock);
2362306a36Sopenharmony_cistatic struct clk *periph_clk;
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_cistatic struct timer_data {
2662306a36Sopenharmony_ci	void	(*cb)(void *);
2762306a36Sopenharmony_ci	void	*data;
2862306a36Sopenharmony_ci} timer_data[BCM63XX_TIMER_COUNT];
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic irqreturn_t timer_interrupt(int irq, void *dev_id)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	u32 stat;
3362306a36Sopenharmony_ci	int i;
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	raw_spin_lock(&timer_reg_lock);
3662306a36Sopenharmony_ci	stat = bcm_timer_readl(TIMER_IRQSTAT_REG);
3762306a36Sopenharmony_ci	bcm_timer_writel(stat, TIMER_IRQSTAT_REG);
3862306a36Sopenharmony_ci	raw_spin_unlock(&timer_reg_lock);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	for (i = 0; i < BCM63XX_TIMER_COUNT; i++) {
4162306a36Sopenharmony_ci		if (!(stat & TIMER_IRQSTAT_TIMER_CAUSE(i)))
4262306a36Sopenharmony_ci			continue;
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci		raw_spin_lock(&timer_data_lock);
4562306a36Sopenharmony_ci		if (!timer_data[i].cb) {
4662306a36Sopenharmony_ci			raw_spin_unlock(&timer_data_lock);
4762306a36Sopenharmony_ci			continue;
4862306a36Sopenharmony_ci		}
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci		timer_data[i].cb(timer_data[i].data);
5162306a36Sopenharmony_ci		raw_spin_unlock(&timer_data_lock);
5262306a36Sopenharmony_ci	}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	return IRQ_HANDLED;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ciint bcm63xx_timer_enable(int id)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	u32 reg;
6062306a36Sopenharmony_ci	unsigned long flags;
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	if (id >= BCM63XX_TIMER_COUNT)
6362306a36Sopenharmony_ci		return -EINVAL;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	raw_spin_lock_irqsave(&timer_reg_lock, flags);
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	reg = bcm_timer_readl(TIMER_CTLx_REG(id));
6862306a36Sopenharmony_ci	reg |= TIMER_CTL_ENABLE_MASK;
6962306a36Sopenharmony_ci	bcm_timer_writel(reg, TIMER_CTLx_REG(id));
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
7262306a36Sopenharmony_ci	reg |= TIMER_IRQSTAT_TIMER_IR_EN(id);
7362306a36Sopenharmony_ci	bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
7662306a36Sopenharmony_ci	return 0;
7762306a36Sopenharmony_ci}
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ciEXPORT_SYMBOL(bcm63xx_timer_enable);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ciint bcm63xx_timer_disable(int id)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	u32 reg;
8462306a36Sopenharmony_ci	unsigned long flags;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (id >= BCM63XX_TIMER_COUNT)
8762306a36Sopenharmony_ci		return -EINVAL;
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci	raw_spin_lock_irqsave(&timer_reg_lock, flags);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	reg = bcm_timer_readl(TIMER_CTLx_REG(id));
9262306a36Sopenharmony_ci	reg &= ~TIMER_CTL_ENABLE_MASK;
9362306a36Sopenharmony_ci	bcm_timer_writel(reg, TIMER_CTLx_REG(id));
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
9662306a36Sopenharmony_ci	reg &= ~TIMER_IRQSTAT_TIMER_IR_EN(id);
9762306a36Sopenharmony_ci	bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
10062306a36Sopenharmony_ci	return 0;
10162306a36Sopenharmony_ci}
10262306a36Sopenharmony_ci
10362306a36Sopenharmony_ciEXPORT_SYMBOL(bcm63xx_timer_disable);
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ciint bcm63xx_timer_register(int id, void (*callback)(void *data), void *data)
10662306a36Sopenharmony_ci{
10762306a36Sopenharmony_ci	unsigned long flags;
10862306a36Sopenharmony_ci	int ret;
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	if (id >= BCM63XX_TIMER_COUNT || !callback)
11162306a36Sopenharmony_ci		return -EINVAL;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	ret = 0;
11462306a36Sopenharmony_ci	raw_spin_lock_irqsave(&timer_data_lock, flags);
11562306a36Sopenharmony_ci	if (timer_data[id].cb) {
11662306a36Sopenharmony_ci		ret = -EBUSY;
11762306a36Sopenharmony_ci		goto out;
11862306a36Sopenharmony_ci	}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	timer_data[id].cb = callback;
12162306a36Sopenharmony_ci	timer_data[id].data = data;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ciout:
12462306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&timer_data_lock, flags);
12562306a36Sopenharmony_ci	return ret;
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ciEXPORT_SYMBOL(bcm63xx_timer_register);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_civoid bcm63xx_timer_unregister(int id)
13162306a36Sopenharmony_ci{
13262306a36Sopenharmony_ci	unsigned long flags;
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	if (id >= BCM63XX_TIMER_COUNT)
13562306a36Sopenharmony_ci		return;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	raw_spin_lock_irqsave(&timer_data_lock, flags);
13862306a36Sopenharmony_ci	timer_data[id].cb = NULL;
13962306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&timer_data_lock, flags);
14062306a36Sopenharmony_ci}
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ciEXPORT_SYMBOL(bcm63xx_timer_unregister);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ciunsigned int bcm63xx_timer_countdown(unsigned int countdown_us)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	return (clk_get_rate(periph_clk) / (1000 * 1000)) * countdown_us;
14762306a36Sopenharmony_ci}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ciEXPORT_SYMBOL(bcm63xx_timer_countdown);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ciint bcm63xx_timer_set(int id, int monotonic, unsigned int countdown_us)
15262306a36Sopenharmony_ci{
15362306a36Sopenharmony_ci	u32 reg, countdown;
15462306a36Sopenharmony_ci	unsigned long flags;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	if (id >= BCM63XX_TIMER_COUNT)
15762306a36Sopenharmony_ci		return -EINVAL;
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	countdown = bcm63xx_timer_countdown(countdown_us);
16062306a36Sopenharmony_ci	if (countdown & ~TIMER_CTL_COUNTDOWN_MASK)
16162306a36Sopenharmony_ci		return -EINVAL;
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	raw_spin_lock_irqsave(&timer_reg_lock, flags);
16462306a36Sopenharmony_ci	reg = bcm_timer_readl(TIMER_CTLx_REG(id));
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (monotonic)
16762306a36Sopenharmony_ci		reg &= ~TIMER_CTL_MONOTONIC_MASK;
16862306a36Sopenharmony_ci	else
16962306a36Sopenharmony_ci		reg |= TIMER_CTL_MONOTONIC_MASK;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	reg &= ~TIMER_CTL_COUNTDOWN_MASK;
17262306a36Sopenharmony_ci	reg |= countdown;
17362306a36Sopenharmony_ci	bcm_timer_writel(reg, TIMER_CTLx_REG(id));
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(&timer_reg_lock, flags);
17662306a36Sopenharmony_ci	return 0;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ciEXPORT_SYMBOL(bcm63xx_timer_set);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ciint bcm63xx_timer_init(void)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	int ret, irq;
18462306a36Sopenharmony_ci	u32 reg;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	reg = bcm_timer_readl(TIMER_IRQSTAT_REG);
18762306a36Sopenharmony_ci	reg &= ~TIMER_IRQSTAT_TIMER0_IR_EN;
18862306a36Sopenharmony_ci	reg &= ~TIMER_IRQSTAT_TIMER1_IR_EN;
18962306a36Sopenharmony_ci	reg &= ~TIMER_IRQSTAT_TIMER2_IR_EN;
19062306a36Sopenharmony_ci	bcm_timer_writel(reg, TIMER_IRQSTAT_REG);
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	periph_clk = clk_get(NULL, "periph");
19362306a36Sopenharmony_ci	if (IS_ERR(periph_clk))
19462306a36Sopenharmony_ci		return -ENODEV;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	irq = bcm63xx_get_irq_number(IRQ_TIMER);
19762306a36Sopenharmony_ci	ret = request_irq(irq, timer_interrupt, 0, "bcm63xx_timer", NULL);
19862306a36Sopenharmony_ci	if (ret) {
19962306a36Sopenharmony_ci		pr_err("%s: failed to register irq\n", __func__);
20062306a36Sopenharmony_ci		return ret;
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	return 0;
20462306a36Sopenharmony_ci}
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ciarch_initcall(bcm63xx_timer_init);
207