1/* 2 * Marvell Orion SoC timer handling. 3 * 4 * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> 5 * 6 * This file is licensed under the terms of the GNU General Public 7 * License version 2. This program is licensed "as is" without any 8 * warranty of any kind, whether express or implied. 9 * 10 * Timer 0 is used as free-running clocksource, while timer 1 is 11 * used as clock_event_device. 12 */ 13 14#include <linux/kernel.h> 15#include <linux/bitops.h> 16#include <linux/clk.h> 17#include <linux/clockchips.h> 18#include <linux/interrupt.h> 19#include <linux/of_address.h> 20#include <linux/of_irq.h> 21#include <linux/spinlock.h> 22#include <linux/sched_clock.h> 23 24#define TIMER_CTRL 0x00 25#define TIMER0_EN BIT(0) 26#define TIMER0_RELOAD_EN BIT(1) 27#define TIMER1_EN BIT(2) 28#define TIMER1_RELOAD_EN BIT(3) 29#define TIMER0_RELOAD 0x10 30#define TIMER0_VAL 0x14 31#define TIMER1_RELOAD 0x18 32#define TIMER1_VAL 0x1c 33 34#define ORION_ONESHOT_MIN 1 35#define ORION_ONESHOT_MAX 0xfffffffe 36 37static void __iomem *timer_base; 38 39/* 40 * Free-running clocksource handling. 41 */ 42static u64 notrace orion_read_sched_clock(void) 43{ 44 return ~readl(timer_base + TIMER0_VAL); 45} 46 47/* 48 * Clockevent handling. 49 */ 50static u32 ticks_per_jiffy; 51 52static int orion_clkevt_next_event(unsigned long delta, 53 struct clock_event_device *dev) 54{ 55 /* setup and enable one-shot timer */ 56 writel(delta, timer_base + TIMER1_VAL); 57 atomic_io_modify(timer_base + TIMER_CTRL, 58 TIMER1_RELOAD_EN | TIMER1_EN, TIMER1_EN); 59 60 return 0; 61} 62 63static void orion_clkevt_mode(enum clock_event_mode mode, 64 struct clock_event_device *dev) 65{ 66 if (mode == CLOCK_EVT_MODE_PERIODIC) { 67 /* setup and enable periodic timer at 1/HZ intervals */ 68 writel(ticks_per_jiffy - 1, timer_base + TIMER1_RELOAD); 69 writel(ticks_per_jiffy - 1, timer_base + TIMER1_VAL); 70 atomic_io_modify(timer_base + TIMER_CTRL, 71 TIMER1_RELOAD_EN | TIMER1_EN, 72 TIMER1_RELOAD_EN | TIMER1_EN); 73 } else { 74 /* disable timer */ 75 atomic_io_modify(timer_base + TIMER_CTRL, 76 TIMER1_RELOAD_EN | TIMER1_EN, 0); 77 } 78} 79 80static struct clock_event_device orion_clkevt = { 81 .name = "orion_event", 82 .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, 83 .shift = 32, 84 .rating = 300, 85 .set_next_event = orion_clkevt_next_event, 86 .set_mode = orion_clkevt_mode, 87}; 88 89static irqreturn_t orion_clkevt_irq_handler(int irq, void *dev_id) 90{ 91 orion_clkevt.event_handler(&orion_clkevt); 92 return IRQ_HANDLED; 93} 94 95static struct irqaction orion_clkevt_irq = { 96 .name = "orion_event", 97 .flags = IRQF_TIMER, 98 .handler = orion_clkevt_irq_handler, 99}; 100 101static void __init orion_timer_init(struct device_node *np) 102{ 103 struct clk *clk; 104 int irq; 105 106 /* timer registers are shared with watchdog timer */ 107 timer_base = of_iomap(np, 0); 108 if (!timer_base) 109 panic("%s: unable to map resource\n", np->name); 110 111 clk = of_clk_get(np, 0); 112 if (IS_ERR(clk)) 113 panic("%s: unable to get clk\n", np->name); 114 clk_prepare_enable(clk); 115 116 /* we are only interested in timer1 irq */ 117 irq = irq_of_parse_and_map(np, 1); 118 if (irq <= 0) 119 panic("%s: unable to parse timer1 irq\n", np->name); 120 121 /* setup timer0 as free-running clocksource */ 122 writel(~0, timer_base + TIMER0_VAL); 123 writel(~0, timer_base + TIMER0_RELOAD); 124 atomic_io_modify(timer_base + TIMER_CTRL, 125 TIMER0_RELOAD_EN | TIMER0_EN, 126 TIMER0_RELOAD_EN | TIMER0_EN); 127 clocksource_mmio_init(timer_base + TIMER0_VAL, "orion_clocksource", 128 clk_get_rate(clk), 300, 32, 129 clocksource_mmio_readl_down); 130 sched_clock_register(orion_read_sched_clock, 32, clk_get_rate(clk)); 131 132 /* setup timer1 as clockevent timer */ 133 if (setup_irq(irq, &orion_clkevt_irq)) 134 panic("%s: unable to setup irq\n", np->name); 135 136 ticks_per_jiffy = (clk_get_rate(clk) + HZ/2) / HZ; 137 orion_clkevt.cpumask = cpumask_of(0); 138 orion_clkevt.irq = irq; 139 clockevents_config_and_register(&orion_clkevt, clk_get_rate(clk), 140 ORION_ONESHOT_MIN, ORION_ONESHOT_MAX); 141} 142CLOCKSOURCE_OF_DECLARE(orion_timer, "marvell,orion-timer", orion_timer_init); 143