Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

clocksource: sunxi: Add Allwinner A1X Timer Driver

Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
CC: Thomas Gleixner <tglx@linutronix.de>
CC: John Stultz <johnstul@us.ibm.com>

+215
+17
Documentation/devicetree/bindings/timer/allwinner,sunxi-timer.txt
··· 1 + Allwinner A1X SoCs Timer Controller 2 + 3 + Required properties: 4 + 5 + - compatible : should be "allwinner,sunxi-timer" 6 + - reg : Specifies base physical address and size of the registers. 7 + - interrupts : The interrupt of the first timer 8 + - clocks: phandle to the source clock (usually a 24 MHz fixed clock) 9 + 10 + Example: 11 + 12 + timer { 13 + compatible = "allwinner,sunxi-timer"; 14 + reg = <0x01c20c00 0x400>; 15 + interrupts = <22>; 16 + clocks = <&osc>; 17 + };
+3
drivers/clocksource/Kconfig
··· 22 22 config ARMADA_370_XP_TIMER 23 23 bool 24 24 25 + config SUNXI_TIMER 26 + bool 27 + 25 28 config CLKSRC_DBX500_PRCMU 26 29 bool "Clocksource PRCMU Timer" 27 30 depends on UX500_SOC_DB8500
+1
drivers/clocksource/Makefile
··· 14 14 obj-$(CONFIG_CLKSRC_DBX500_PRCMU) += clksrc-dbx500-prcmu.o 15 15 obj-$(CONFIG_ARMADA_370_XP_TIMER) += time-armada-370-xp.o 16 16 obj-$(CONFIG_ARCH_BCM2835) += bcm2835_timer.o 17 + obj-$(CONFIG_SUNXI_TIMER) += sunxi_timer.o 17 18 18 19 obj-$(CONFIG_CLKSRC_ARM_GENERIC) += arm_generic.o
+170
drivers/clocksource/sunxi_timer.c
··· 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 + #include <linux/sunxi_timer.h> 26 + #include <linux/clk/sunxi.h> 27 + 28 + #define TIMER_CTL_REG 0x00 29 + #define TIMER_CTL_ENABLE (1 << 0) 30 + #define TIMER_IRQ_ST_REG 0x04 31 + #define TIMER0_CTL_REG 0x10 32 + #define TIMER0_CTL_ENABLE (1 << 0) 33 + #define TIMER0_CTL_AUTORELOAD (1 << 1) 34 + #define TIMER0_CTL_ONESHOT (1 << 7) 35 + #define TIMER0_INTVAL_REG 0x14 36 + #define TIMER0_CNTVAL_REG 0x18 37 + 38 + #define TIMER_SCAL 16 39 + 40 + static void __iomem *timer_base; 41 + 42 + static void sunxi_clkevt_mode(enum clock_event_mode mode, 43 + struct clock_event_device *clk) 44 + { 45 + u32 u = readl(timer_base + TIMER0_CTL_REG); 46 + 47 + switch (mode) { 48 + case CLOCK_EVT_MODE_PERIODIC: 49 + u &= ~(TIMER0_CTL_ONESHOT); 50 + writel(u | TIMER0_CTL_ENABLE, timer_base + TIMER0_CTL_REG); 51 + break; 52 + 53 + case CLOCK_EVT_MODE_ONESHOT: 54 + writel(u | TIMER0_CTL_ONESHOT, timer_base + TIMER0_CTL_REG); 55 + break; 56 + case CLOCK_EVT_MODE_UNUSED: 57 + case CLOCK_EVT_MODE_SHUTDOWN: 58 + default: 59 + writel(u & ~(TIMER0_CTL_ENABLE), timer_base + TIMER0_CTL_REG); 60 + break; 61 + } 62 + } 63 + 64 + static int sunxi_clkevt_next_event(unsigned long evt, 65 + struct clock_event_device *unused) 66 + { 67 + u32 u = readl(timer_base + TIMER0_CTL_REG); 68 + writel(evt, timer_base + TIMER0_CNTVAL_REG); 69 + writel(u | TIMER0_CTL_ENABLE | TIMER0_CTL_AUTORELOAD, 70 + timer_base + TIMER0_CTL_REG); 71 + 72 + return 0; 73 + } 74 + 75 + static struct clock_event_device sunxi_clockevent = { 76 + .name = "sunxi_tick", 77 + .shift = 32, 78 + .rating = 300, 79 + .features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT, 80 + .set_mode = sunxi_clkevt_mode, 81 + .set_next_event = sunxi_clkevt_next_event, 82 + }; 83 + 84 + 85 + static irqreturn_t sunxi_timer_interrupt(int irq, void *dev_id) 86 + { 87 + struct clock_event_device *evt = (struct clock_event_device *)dev_id; 88 + 89 + writel(0x1, timer_base + TIMER_IRQ_ST_REG); 90 + evt->event_handler(evt); 91 + 92 + return IRQ_HANDLED; 93 + } 94 + 95 + static struct irqaction sunxi_timer_irq = { 96 + .name = "sunxi_timer0", 97 + .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, 98 + .handler = sunxi_timer_interrupt, 99 + .dev_id = &sunxi_clockevent, 100 + }; 101 + 102 + static struct of_device_id sunxi_timer_dt_ids[] = { 103 + { .compatible = "allwinner,sunxi-timer" }, 104 + }; 105 + 106 + static void __init sunxi_timer_init(void) 107 + { 108 + struct device_node *node; 109 + unsigned long rate = 0; 110 + struct clk *clk; 111 + int ret, irq; 112 + u32 val; 113 + 114 + node = of_find_matching_node(NULL, sunxi_timer_dt_ids); 115 + if (!node) 116 + panic("No sunxi timer node"); 117 + 118 + timer_base = of_iomap(node, 0); 119 + if (!timer_base) 120 + panic("Can't map registers"); 121 + 122 + irq = irq_of_parse_and_map(node, 0); 123 + if (irq <= 0) 124 + panic("Can't parse IRQ"); 125 + 126 + sunxi_init_clocks(); 127 + 128 + clk = of_clk_get(node, 0); 129 + if (IS_ERR(clk)) 130 + panic("Can't get timer clock"); 131 + 132 + rate = clk_get_rate(clk); 133 + 134 + writel(rate / (TIMER_SCAL * HZ), 135 + timer_base + TIMER0_INTVAL_REG); 136 + 137 + /* set clock source to HOSC, 16 pre-division */ 138 + val = readl(timer_base + TIMER0_CTL_REG); 139 + val &= ~(0x07 << 4); 140 + val &= ~(0x03 << 2); 141 + val |= (4 << 4) | (1 << 2); 142 + writel(val, timer_base + TIMER0_CTL_REG); 143 + 144 + /* set mode to auto reload */ 145 + val = readl(timer_base + TIMER0_CTL_REG); 146 + writel(val | TIMER0_CTL_AUTORELOAD, timer_base + TIMER0_CTL_REG); 147 + 148 + ret = setup_irq(irq, &sunxi_timer_irq); 149 + if (ret) 150 + pr_warn("failed to setup irq %d\n", irq); 151 + 152 + /* Enable timer0 interrupt */ 153 + val = readl(timer_base + TIMER_CTL_REG); 154 + writel(val | TIMER_CTL_ENABLE, timer_base + TIMER_CTL_REG); 155 + 156 + sunxi_clockevent.mult = div_sc(rate / TIMER_SCAL, 157 + NSEC_PER_SEC, 158 + sunxi_clockevent.shift); 159 + sunxi_clockevent.max_delta_ns = clockevent_delta2ns(0xff, 160 + &sunxi_clockevent); 161 + sunxi_clockevent.min_delta_ns = clockevent_delta2ns(0x1, 162 + &sunxi_clockevent); 163 + sunxi_clockevent.cpumask = cpumask_of(0); 164 + 165 + clockevents_register_device(&sunxi_clockevent); 166 + } 167 + 168 + struct sys_timer sunxi_timer = { 169 + .init = sunxi_timer_init, 170 + };
+24
include/linux/sunxi_timer.h
··· 1 + /* 2 + * Copyright 2012 Maxime Ripard 3 + * 4 + * Maxime Ripard <maxime.ripard@free-electrons.com> 5 + * 6 + * This program is free software; you can redistribute it and/or modify 7 + * it under the terms of the GNU General Public License as published by 8 + * the Free Software Foundation; either version 2 of the License, or 9 + * (at your option) any later version. 10 + * 11 + * This program is distributed in the hope that it will be useful, 12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 + * GNU General Public License for more details. 15 + */ 16 + 17 + #ifndef __SUNXI_TIMER_H 18 + #define __SUNXI_TIMER_H 19 + 20 + #include <asm/mach/time.h> 21 + 22 + extern struct sys_timer sunxi_timer; 23 + 24 + #endif