diff options
Diffstat (limited to 'drivers/clocksource/sun4i_timer.c')
-rw-r--r-- | drivers/clocksource/sun4i_timer.c | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/drivers/clocksource/sun4i_timer.c b/drivers/clocksource/sun4i_timer.c new file mode 100644 index 000000000000..d4674e78ef35 --- /dev/null +++ b/drivers/clocksource/sun4i_timer.c | |||
@@ -0,0 +1,148 @@ | |||
1 | /* | ||
2 | * Allwinner A1X SoCs timer handling. | ||
3 | * | ||
4 | * Copyright (C) 2012 Maxime Ripard | ||
5 | * | ||
6 | * Maxime Ripard <maxime.ripard@free-electrons.com> | ||
7 | * | ||
8 | * Based on code from | ||
9 | * Allwinner Technology Co., Ltd. <www.allwinnertech.com> | ||
10 | * Benn Huang <benn@allwinnertech.com> | ||
11 | * | ||
12 | * This file is licensed under the terms of the GNU General Public | ||
13 | * License version 2. This program is licensed "as is" without any | ||
14 | * warranty of any kind, whether express or implied. | ||
15 | */ | ||
16 | |||
17 | #include <linux/clk.h> | ||
18 | #include <linux/clockchips.h> | ||
19 | #include <linux/interrupt.h> | ||
20 | #include <linux/irq.h> | ||
21 | #include <linux/irqreturn.h> | ||
22 | #include <linux/of.h> | ||
23 | #include <linux/of_address.h> | ||
24 | #include <linux/of_irq.h> | ||
25 | |||
26 | #define TIMER_IRQ_EN_REG 0x00 | ||
27 | #define TIMER_IRQ_EN(val) (1 << val) | ||
28 | #define TIMER_IRQ_ST_REG 0x04 | ||
29 | #define TIMER_CTL_REG(val) (0x10 * val + 0x10) | ||
30 | #define TIMER_CTL_ENABLE (1 << 0) | ||
31 | #define TIMER_CTL_AUTORELOAD (1 << 1) | ||
32 | #define TIMER_CTL_ONESHOT (1 << 7) | ||
33 | #define TIMER_INTVAL_REG(val) (0x10 * val + 0x14) | ||
34 | #define TIMER_CNTVAL_REG(val) (0x10 * val + 0x18) | ||
35 | |||
36 | #define TIMER_SCAL 16 | ||
37 | |||
38 | static void __iomem *timer_base; | ||
39 | |||
40 | static void sun4i_clkevt_mode(enum clock_event_mode mode, | ||
41 | struct clock_event_device *clk) | ||
42 | { | ||
43 | u32 u = readl(timer_base + TIMER_CTL_REG(0)); | ||
44 | |||
45 | switch (mode) { | ||
46 | case CLOCK_EVT_MODE_PERIODIC: | ||
47 | u &= ~(TIMER_CTL_ONESHOT); | ||
48 | writel(u | TIMER_CTL_ENABLE, timer_base + TIMER_CTL_REG(0)); | ||
49 | break; | ||
50 | |||
51 | case CLOCK_EVT_MODE_ONESHOT: | ||
52 | writel(u | TIMER_CTL_ONESHOT, timer_base + TIMER_CTL_REG(0)); | ||
53 | break; | ||
54 | case CLOCK_EVT_MODE_UNUSED: | ||
55 | case CLOCK_EVT_MODE_SHUTDOWN: | ||
56 | default: | ||
57 | writel(u & ~(TIMER_CTL_ENABLE), timer_base + TIMER_CTL_REG(0)); | ||
58 | break; | ||
59 | } | ||
60 | } | ||
61 | |||
62 | static int sun4i_clkevt_next_event(unsigned long evt, | ||
63 | struct clock_event_device *unused) | ||
64 | { | ||
65 | u32 u = readl(timer_base + TIMER_CTL_REG(0)); | ||
66 | writel(evt, timer_base + TIMER_CNTVAL_REG(0)); | ||
67 | writel(u | TIMER_CTL_ENABLE | TIMER_CTL_AUTORELOAD, | ||
68 | timer_base + TIMER_CTL_REG(0)); | ||
69 | |||
70 | return 0; | ||
71 | } | ||
72 | |||
73 | static struct clock_event_device sun4i_clockevent = { | ||
74 | .name = "sun4i_tick", | ||
75 | .rating = 300, | ||
76 | .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, | ||
77 | .set_mode = sun4i_clkevt_mode, | ||
78 | .set_next_event = sun4i_clkevt_next_event, | ||
79 | }; | ||
80 | |||
81 | |||
82 | static irqreturn_t sun4i_timer_interrupt(int irq, void *dev_id) | ||
83 | { | ||
84 | struct clock_event_device *evt = (struct clock_event_device *)dev_id; | ||
85 | |||
86 | writel(0x1, timer_base + TIMER_IRQ_ST_REG); | ||
87 | evt->event_handler(evt); | ||
88 | |||
89 | return IRQ_HANDLED; | ||
90 | } | ||
91 | |||
92 | static struct irqaction sun4i_timer_irq = { | ||
93 | .name = "sun4i_timer0", | ||
94 | .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, | ||
95 | .handler = sun4i_timer_interrupt, | ||
96 | .dev_id = &sun4i_clockevent, | ||
97 | }; | ||
98 | |||
99 | static void __init sun4i_timer_init(struct device_node *node) | ||
100 | { | ||
101 | unsigned long rate = 0; | ||
102 | struct clk *clk; | ||
103 | int ret, irq; | ||
104 | u32 val; | ||
105 | |||
106 | timer_base = of_iomap(node, 0); | ||
107 | if (!timer_base) | ||
108 | panic("Can't map registers"); | ||
109 | |||
110 | irq = irq_of_parse_and_map(node, 0); | ||
111 | if (irq <= 0) | ||
112 | panic("Can't parse IRQ"); | ||
113 | |||
114 | clk = of_clk_get(node, 0); | ||
115 | if (IS_ERR(clk)) | ||
116 | panic("Can't get timer clock"); | ||
117 | |||
118 | rate = clk_get_rate(clk); | ||
119 | |||
120 | writel(rate / (TIMER_SCAL * HZ), | ||
121 | timer_base + TIMER_INTVAL_REG(0)); | ||
122 | |||
123 | /* set clock source to HOSC, 16 pre-division */ | ||
124 | val = readl(timer_base + TIMER_CTL_REG(0)); | ||
125 | val &= ~(0x07 << 4); | ||
126 | val &= ~(0x03 << 2); | ||
127 | val |= (4 << 4) | (1 << 2); | ||
128 | writel(val, timer_base + TIMER_CTL_REG(0)); | ||
129 | |||
130 | /* set mode to auto reload */ | ||
131 | val = readl(timer_base + TIMER_CTL_REG(0)); | ||
132 | writel(val | TIMER_CTL_AUTORELOAD, timer_base + TIMER_CTL_REG(0)); | ||
133 | |||
134 | ret = setup_irq(irq, &sun4i_timer_irq); | ||
135 | if (ret) | ||
136 | pr_warn("failed to setup irq %d\n", irq); | ||
137 | |||
138 | /* Enable timer0 interrupt */ | ||
139 | val = readl(timer_base + TIMER_IRQ_EN_REG); | ||
140 | writel(val | TIMER_IRQ_EN(0), timer_base + TIMER_IRQ_EN_REG); | ||
141 | |||
142 | sun4i_clockevent.cpumask = cpumask_of(0); | ||
143 | |||
144 | clockevents_config_and_register(&sun4i_clockevent, rate / TIMER_SCAL, | ||
145 | 0x1, 0xff); | ||
146 | } | ||
147 | CLOCKSOURCE_OF_DECLARE(sun4i, "allwinner,sun4i-timer", | ||
148 | sun4i_timer_init); | ||