| 1 | |
| 2 | |
| 3 | * Copyright (C) 2012 Altera Corporation
|
| 4 | * Copyright (c) 2011 Picochip Ltd., Jamie Iles
|
| 5 | *
|
| 6 | * Modified from mach-picoxcell/time.c
|
| 7 |
|
| 8 | #include <linux/delay.h>
|
| 9 | #include <linux/dw_apb_timer.h>
|
| 10 | #include <linux/of.h>
|
| 11 | #include <linux/of_address.h>
|
| 12 | #include <linux/of_irq.h>
|
| 13 | #include <linux/clk.h>
|
| 14 | #include <linux/reset.h>
|
| 15 | #include <linux/sched_clock.h>
|
| 16 |
|
| 17 | static int __init timer_get_base_and_rate(struct device_node *np,
|
| 18 | void __iomem **base, u32 *rate)
|
| 19 | {
|
| 20 | struct clk *timer_clk;
|
| 21 | struct clk *pclk;
|
| 22 | struct reset_control *rstc;
|
| 23 | int ret;
|
| 24 |
|
| 25 | *base = of_iomap(np, 0);
|
| 26 |
|
| 27 | if (!*base)
|
| 28 | panic("Unable to map regs for %pOFn", np);
|
| 29 |
|
| 30 | |
| 31 | * Reset the timer if the reset control is available, wiping
|
| 32 | * out the state the firmware may have left it
|
| 33 |
|
| 34 | rstc = of_reset_control_get(np, NULL);
|
| 35 | if (!IS_ERR(rstc)) {
|
| 36 | reset_control_assert(rstc);
|
| 37 | reset_control_deassert(rstc);
|
| 38 | }
|
| 39 |
|
| 40 | |
| 41 | * Not all implementations use a peripheral clock, so don't panic
|
| 42 | * if it's not present
|
| 43 |
|
| 44 | pclk = of_clk_get_by_name(np, "pclk");
|
| 45 | if (!IS_ERR(pclk))
|
| 46 | if (clk_prepare_enable(pclk))
|
| 47 | pr_warn("pclk for %pOFn is present, but could not be activated\n",
|
| 48 | np);
|
| 49 |
|
| 50 | if (!of_property_read_u32(np, "clock-freq", rate) ||
|
| 51 | !of_property_read_u32(np, "clock-frequency", rate))
|
| 52 | return 0;
|
| 53 |
|
| 54 | timer_clk = of_clk_get_by_name(np, "timer");
|
| 55 | if (IS_ERR(timer_clk)) {
|
| 56 | ret = PTR_ERR(timer_clk);
|
| 57 | goto out_pclk_disable;
|
| 58 | }
|
| 59 |
|
| 60 | ret = clk_prepare_enable(timer_clk);
|
| 61 | if (ret)
|
| 62 | goto out_timer_clk_put;
|
| 63 |
|
| 64 | *rate = clk_get_rate(timer_clk);
|
| 65 | if (!(*rate)) {
|
| 66 | ret = -EINVAL;
|
| 67 | goto out_timer_clk_disable;
|
| 68 | }
|
| 69 |
|
| 70 | return 0;
|
| 71 |
|
| 72 | out_timer_clk_disable:
|
| 73 | clk_disable_unprepare(timer_clk);
|
| 74 | out_timer_clk_put:
|
| 75 | clk_put(timer_clk);
|
| 76 | out_pclk_disable:
|
| 77 | if (!IS_ERR(pclk)) {
|
| 78 | clk_disable_unprepare(pclk);
|
| 79 | clk_put(pclk);
|
| 80 | }
|
| 81 | iounmap(*base);
|
| 82 | return ret;
|
| 83 | }
|
| 84 |
|
| 85 | static int __init add_clockevent(struct device_node *event_timer)
|
| 86 | {
|
| 87 | void __iomem *iobase;
|
| 88 | struct dw_apb_clock_event_device *ced;
|
| 89 | u32 irq, rate;
|
| 90 | int ret = 0;
|
| 91 |
|
| 92 | irq = irq_of_parse_and_map(event_timer, 0);
|
| 93 | if (irq == 0)
|
| 94 | panic("No IRQ for clock event timer");
|
| 95 |
|
| 96 | ret = timer_get_base_and_rate(event_timer, &iobase, &rate);
|
| 97 | if (ret)
|
| 98 | return ret;
|
| 99 |
|
| 100 | ced = dw_apb_clockevent_init(-1, event_timer->name, 300, iobase, irq,
|
| 101 | rate);
|
| 102 | if (!ced)
|
| 103 | return -EINVAL;
|
| 104 |
|
| 105 | dw_apb_clockevent_register(ced);
|
| 106 |
|
| 107 | return 0;
|
| 108 | }
|
| 109 |
|
| 110 | static void __iomem *sched_io_base;
|
| 111 | static u32 sched_rate;
|
| 112 |
|
| 113 | static int __init add_clocksource(struct device_node *source_timer)
|
| 114 | {
|
| 115 | void __iomem *iobase;
|
| 116 | struct dw_apb_clocksource *cs;
|
| 117 | u32 rate;
|
| 118 | int ret;
|
| 119 |
|
| 120 | ret = timer_get_base_and_rate(source_timer, &iobase, &rate);
|
| 121 | if (ret)
|
| 122 | return ret;
|
| 123 |
|
| 124 | cs = dw_apb_clocksource_init(300, source_timer->name, iobase, rate);
|
| 125 | if (!cs)
|
| 126 | return -EINVAL;
|
| 127 |
|
| 128 | dw_apb_clocksource_start(cs);
|
| 129 | dw_apb_clocksource_register(cs);
|
| 130 |
|
| 131 | |
| 132 | * Fallback to use the clocksource as sched_clock if no separate
|
| 133 | * timer is found. sched_io_base then points to the current_value
|
| 134 | * register of the clocksource timer.
|
| 135 |
|
| 136 | sched_io_base = iobase + 0x04;
|
| 137 | sched_rate = rate;
|
| 138 |
|
| 139 | return 0;
|
| 140 | }
|
| 141 |
|
| 142 | static u64 notrace read_sched_clock(void)
|
| 143 | {
|
| 144 | return ~readl_relaxed(sched_io_base);
|
| 145 | }
|
| 146 |
|
| 147 | static const struct of_device_id sptimer_ids[] __initconst = {
|
| 148 | { .compatible = "picochip,pc3x2-rtc" },
|
| 149 | { },
|
| 150 | };
|
| 151 |
|
| 152 | static void __init init_sched_clock(void)
|
| 153 | {
|
| 154 | struct device_node *sched_timer;
|
| 155 |
|
| 156 | sched_timer = of_find_matching_node(NULL, sptimer_ids);
|
| 157 | if (sched_timer) {
|
| 158 | timer_get_base_and_rate(sched_timer, &sched_io_base,
|
| 159 | &sched_rate);
|
| 160 | of_node_put(sched_timer);
|
| 161 | }
|
| 162 |
|
| 163 | sched_clock_register(read_sched_clock, 32, sched_rate);
|
| 164 | }
|
| 165 |
|
| 166 | #ifdef CONFIG_ARM
|
| 167 | static unsigned long dw_apb_delay_timer_read(void)
|
| 168 | {
|
| 169 | return ~readl_relaxed(sched_io_base);
|
| 170 | }
|
| 171 |
|
| 172 | static struct delay_timer dw_apb_delay_timer = {
|
| 173 | .read_current_timer = dw_apb_delay_timer_read,
|
| 174 | };
|
| 175 | #endif
|
| 176 |
|
| 177 | static int num_called;
|
| 178 | static int __init dw_apb_timer_init(struct device_node *timer)
|
| 179 | {
|
| 180 | int ret = 0;
|
| 181 |
|
| 182 | switch (num_called) {
|
| 183 | case 1:
|
| 184 | pr_debug("%s: found clocksource timer\n", __func__);
|
| 185 | ret = add_clocksource(timer);
|
| 186 | if (ret)
|
| 187 | return ret;
|
| 188 | init_sched_clock();
|
| 189 | #ifdef CONFIG_ARM
|
| 190 | dw_apb_delay_timer.freq = sched_rate;
|
| 191 | register_current_timer_delay(&dw_apb_delay_timer);
|
| 192 | #endif
|
| 193 | break;
|
| 194 | default:
|
| 195 | pr_debug("%s: found clockevent timer\n", __func__);
|
| 196 | ret = add_clockevent(timer);
|
| 197 | if (ret)
|
| 198 | return ret;
|
| 199 | break;
|
| 200 | }
|
| 201 |
|
| 202 | num_called++;
|
| 203 |
|
| 204 | return 0;
|
| 205 | }
|
| 206 | TIMER_OF_DECLARE(pc3x2_timer, "picochip,pc3x2-timer", dw_apb_timer_init);
|
| 207 | TIMER_OF_DECLARE(apb_timer_osc, "snps,dw-apb-timer-osc", dw_apb_timer_init);
|
| 208 | TIMER_OF_DECLARE(apb_timer_sp, "snps,dw-apb-timer-sp", dw_apb_timer_init);
|
| 209 | TIMER_OF_DECLARE(apb_timer, "snps,dw-apb-timer", dw_apb_timer_init);
|