18d8bd7beSOleksij Rempel /* 28d8bd7beSOleksij Rempel * Copyright (C) 2014 Oleksij Rempel <linux@rempel-privat.de> 38d8bd7beSOleksij Rempel * 48d8bd7beSOleksij Rempel * This program is free software; you can redistribute it and/or 58d8bd7beSOleksij Rempel * modify it under the terms of the GNU General Public License 68d8bd7beSOleksij Rempel * as published by the Free Software Foundation; either version 2 78d8bd7beSOleksij Rempel * of the License, or (at your option) any later version. 88d8bd7beSOleksij Rempel */ 98d8bd7beSOleksij Rempel 108d8bd7beSOleksij Rempel #include <linux/kernel.h> 118d8bd7beSOleksij Rempel #include <linux/init.h> 128d8bd7beSOleksij Rempel #include <linux/interrupt.h> 138d8bd7beSOleksij Rempel #include <linux/sched.h> 148d8bd7beSOleksij Rempel #include <linux/clk.h> 158d8bd7beSOleksij Rempel #include <linux/clocksource.h> 168d8bd7beSOleksij Rempel #include <linux/clockchips.h> 178d8bd7beSOleksij Rempel #include <linux/io.h> 188d8bd7beSOleksij Rempel #include <linux/of.h> 198d8bd7beSOleksij Rempel #include <linux/of_address.h> 208d8bd7beSOleksij Rempel #include <linux/of_irq.h> 218d8bd7beSOleksij Rempel #include <linux/bitops.h> 228d8bd7beSOleksij Rempel 238d8bd7beSOleksij Rempel #define DRIVER_NAME "asm9260-timer" 248d8bd7beSOleksij Rempel 258d8bd7beSOleksij Rempel /* 268d8bd7beSOleksij Rempel * this device provide 4 offsets for each register: 278d8bd7beSOleksij Rempel * 0x0 - plain read write mode 288d8bd7beSOleksij Rempel * 0x4 - set mode, OR logic. 298d8bd7beSOleksij Rempel * 0x8 - clr mode, XOR logic. 308d8bd7beSOleksij Rempel * 0xc - togle mode. 318d8bd7beSOleksij Rempel */ 328d8bd7beSOleksij Rempel #define SET_REG 4 338d8bd7beSOleksij Rempel #define CLR_REG 8 348d8bd7beSOleksij Rempel 358d8bd7beSOleksij Rempel #define HW_IR 0x0000 /* RW. Interrupt */ 368d8bd7beSOleksij Rempel #define BM_IR_CR0 BIT(4) 378d8bd7beSOleksij Rempel #define BM_IR_MR3 BIT(3) 388d8bd7beSOleksij Rempel #define BM_IR_MR2 BIT(2) 398d8bd7beSOleksij Rempel #define BM_IR_MR1 BIT(1) 408d8bd7beSOleksij Rempel #define BM_IR_MR0 BIT(0) 418d8bd7beSOleksij Rempel 428d8bd7beSOleksij Rempel #define HW_TCR 0x0010 /* RW. Timer controller */ 438d8bd7beSOleksij Rempel /* BM_C*_RST 448d8bd7beSOleksij Rempel * Timer Counter and the Prescale Counter are synchronously reset on the 458d8bd7beSOleksij Rempel * next positive edge of PCLK. The counters remain reset until TCR[1] is 468d8bd7beSOleksij Rempel * returned to zero. */ 478d8bd7beSOleksij Rempel #define BM_C3_RST BIT(7) 488d8bd7beSOleksij Rempel #define BM_C2_RST BIT(6) 498d8bd7beSOleksij Rempel #define BM_C1_RST BIT(5) 508d8bd7beSOleksij Rempel #define BM_C0_RST BIT(4) 518d8bd7beSOleksij Rempel /* BM_C*_EN 528d8bd7beSOleksij Rempel * 1 - Timer Counter and Prescale Counter are enabled for counting 538d8bd7beSOleksij Rempel * 0 - counters are disabled */ 548d8bd7beSOleksij Rempel #define BM_C3_EN BIT(3) 558d8bd7beSOleksij Rempel #define BM_C2_EN BIT(2) 568d8bd7beSOleksij Rempel #define BM_C1_EN BIT(1) 578d8bd7beSOleksij Rempel #define BM_C0_EN BIT(0) 588d8bd7beSOleksij Rempel 598d8bd7beSOleksij Rempel #define HW_DIR 0x0020 /* RW. Direction? */ 608d8bd7beSOleksij Rempel /* 00 - count up 618d8bd7beSOleksij Rempel * 01 - count down 628d8bd7beSOleksij Rempel * 10 - ?? 2^n/2 */ 638d8bd7beSOleksij Rempel #define BM_DIR_COUNT_UP 0 648d8bd7beSOleksij Rempel #define BM_DIR_COUNT_DOWN 1 658d8bd7beSOleksij Rempel #define BM_DIR0_SHIFT 0 668d8bd7beSOleksij Rempel #define BM_DIR1_SHIFT 4 678d8bd7beSOleksij Rempel #define BM_DIR2_SHIFT 8 688d8bd7beSOleksij Rempel #define BM_DIR3_SHIFT 12 698d8bd7beSOleksij Rempel #define BM_DIR_DEFAULT (BM_DIR_COUNT_UP << BM_DIR0_SHIFT | \ 708d8bd7beSOleksij Rempel BM_DIR_COUNT_UP << BM_DIR1_SHIFT | \ 718d8bd7beSOleksij Rempel BM_DIR_COUNT_UP << BM_DIR2_SHIFT | \ 728d8bd7beSOleksij Rempel BM_DIR_COUNT_UP << BM_DIR3_SHIFT) 738d8bd7beSOleksij Rempel 748d8bd7beSOleksij Rempel #define HW_TC0 0x0030 /* RO. Timer counter 0 */ 758d8bd7beSOleksij Rempel /* HW_TC*. Timer counter owerflow (0xffff.ffff to 0x0000.0000) do not generate 768d8bd7beSOleksij Rempel * interrupt. This registers can be used to detect overflow */ 778d8bd7beSOleksij Rempel #define HW_TC1 0x0040 788d8bd7beSOleksij Rempel #define HW_TC2 0x0050 798d8bd7beSOleksij Rempel #define HW_TC3 0x0060 808d8bd7beSOleksij Rempel 818d8bd7beSOleksij Rempel #define HW_PR 0x0070 /* RW. prescaler */ 828d8bd7beSOleksij Rempel #define BM_PR_DISABLE 0 838d8bd7beSOleksij Rempel #define HW_PC 0x0080 /* RO. Prescaler counter */ 848d8bd7beSOleksij Rempel #define HW_MCR 0x0090 /* RW. Match control */ 858d8bd7beSOleksij Rempel /* enable interrupt on match */ 868d8bd7beSOleksij Rempel #define BM_MCR_INT_EN(n) (1 << (n * 3 + 0)) 878d8bd7beSOleksij Rempel /* enable TC reset on match */ 888d8bd7beSOleksij Rempel #define BM_MCR_RES_EN(n) (1 << (n * 3 + 1)) 898d8bd7beSOleksij Rempel /* enable stop TC on match */ 908d8bd7beSOleksij Rempel #define BM_MCR_STOP_EN(n) (1 << (n * 3 + 2)) 918d8bd7beSOleksij Rempel 928d8bd7beSOleksij Rempel #define HW_MR0 0x00a0 /* RW. Match reg */ 938d8bd7beSOleksij Rempel #define HW_MR1 0x00b0 948d8bd7beSOleksij Rempel #define HW_MR2 0x00C0 958d8bd7beSOleksij Rempel #define HW_MR3 0x00D0 968d8bd7beSOleksij Rempel 978d8bd7beSOleksij Rempel #define HW_CTCR 0x0180 /* Counter control */ 988d8bd7beSOleksij Rempel #define BM_CTCR0_SHIFT 0 998d8bd7beSOleksij Rempel #define BM_CTCR1_SHIFT 2 1008d8bd7beSOleksij Rempel #define BM_CTCR2_SHIFT 4 1018d8bd7beSOleksij Rempel #define BM_CTCR3_SHIFT 6 1028d8bd7beSOleksij Rempel #define BM_CTCR_TM 0 /* Timer mode. Every rising PCLK edge. */ 1038d8bd7beSOleksij Rempel #define BM_CTCR_DEFAULT (BM_CTCR_TM << BM_CTCR0_SHIFT | \ 1048d8bd7beSOleksij Rempel BM_CTCR_TM << BM_CTCR1_SHIFT | \ 1058d8bd7beSOleksij Rempel BM_CTCR_TM << BM_CTCR2_SHIFT | \ 1068d8bd7beSOleksij Rempel BM_CTCR_TM << BM_CTCR3_SHIFT) 1078d8bd7beSOleksij Rempel 1088d8bd7beSOleksij Rempel static struct asm9260_timer_priv { 1098d8bd7beSOleksij Rempel void __iomem *base; 1108d8bd7beSOleksij Rempel unsigned long ticks_per_jiffy; 1118d8bd7beSOleksij Rempel } priv; 1128d8bd7beSOleksij Rempel 1138d8bd7beSOleksij Rempel static int asm9260_timer_set_next_event(unsigned long delta, 1148d8bd7beSOleksij Rempel struct clock_event_device *evt) 1158d8bd7beSOleksij Rempel { 1168d8bd7beSOleksij Rempel /* configure match count for TC0 */ 1178d8bd7beSOleksij Rempel writel_relaxed(delta, priv.base + HW_MR0); 1188d8bd7beSOleksij Rempel /* enable TC0 */ 1198d8bd7beSOleksij Rempel writel_relaxed(BM_C0_EN, priv.base + HW_TCR + SET_REG); 1208d8bd7beSOleksij Rempel return 0; 1218d8bd7beSOleksij Rempel } 1228d8bd7beSOleksij Rempel 1233465f609SViresh Kumar static inline void __asm9260_timer_shutdown(struct clock_event_device *evt) 1248d8bd7beSOleksij Rempel { 1258d8bd7beSOleksij Rempel /* stop timer0 */ 1268d8bd7beSOleksij Rempel writel_relaxed(BM_C0_EN, priv.base + HW_TCR + CLR_REG); 1273465f609SViresh Kumar } 1288d8bd7beSOleksij Rempel 1293465f609SViresh Kumar static int asm9260_timer_shutdown(struct clock_event_device *evt) 1303465f609SViresh Kumar { 1313465f609SViresh Kumar __asm9260_timer_shutdown(evt); 1323465f609SViresh Kumar return 0; 1333465f609SViresh Kumar } 1343465f609SViresh Kumar 1353465f609SViresh Kumar static int asm9260_timer_set_oneshot(struct clock_event_device *evt) 1363465f609SViresh Kumar { 1373465f609SViresh Kumar __asm9260_timer_shutdown(evt); 1383465f609SViresh Kumar 1393465f609SViresh Kumar /* enable reset and stop on match */ 1403465f609SViresh Kumar writel_relaxed(BM_MCR_RES_EN(0) | BM_MCR_STOP_EN(0), 1413465f609SViresh Kumar priv.base + HW_MCR + SET_REG); 1423465f609SViresh Kumar return 0; 1433465f609SViresh Kumar } 1443465f609SViresh Kumar 1453465f609SViresh Kumar static int asm9260_timer_set_periodic(struct clock_event_device *evt) 1463465f609SViresh Kumar { 1473465f609SViresh Kumar __asm9260_timer_shutdown(evt); 1483465f609SViresh Kumar 1498d8bd7beSOleksij Rempel /* disable reset and stop on match */ 1508d8bd7beSOleksij Rempel writel_relaxed(BM_MCR_RES_EN(0) | BM_MCR_STOP_EN(0), 1518d8bd7beSOleksij Rempel priv.base + HW_MCR + CLR_REG); 1528d8bd7beSOleksij Rempel /* configure match count for TC0 */ 1538d8bd7beSOleksij Rempel writel_relaxed(priv.ticks_per_jiffy, priv.base + HW_MR0); 1548d8bd7beSOleksij Rempel /* enable TC0 */ 1558d8bd7beSOleksij Rempel writel_relaxed(BM_C0_EN, priv.base + HW_TCR + SET_REG); 1563465f609SViresh Kumar return 0; 1578d8bd7beSOleksij Rempel } 1588d8bd7beSOleksij Rempel 1598d8bd7beSOleksij Rempel static struct clock_event_device event_dev = { 1608d8bd7beSOleksij Rempel .name = DRIVER_NAME, 1618d8bd7beSOleksij Rempel .rating = 200, 1623465f609SViresh Kumar .features = CLOCK_EVT_FEAT_PERIODIC | 1633465f609SViresh Kumar CLOCK_EVT_FEAT_ONESHOT, 1648d8bd7beSOleksij Rempel .set_next_event = asm9260_timer_set_next_event, 1653465f609SViresh Kumar .set_state_shutdown = asm9260_timer_shutdown, 1663465f609SViresh Kumar .set_state_periodic = asm9260_timer_set_periodic, 1673465f609SViresh Kumar .set_state_oneshot = asm9260_timer_set_oneshot, 1683465f609SViresh Kumar .tick_resume = asm9260_timer_shutdown, 1698d8bd7beSOleksij Rempel }; 1708d8bd7beSOleksij Rempel 1718d8bd7beSOleksij Rempel static irqreturn_t asm9260_timer_interrupt(int irq, void *dev_id) 1728d8bd7beSOleksij Rempel { 1738d8bd7beSOleksij Rempel struct clock_event_device *evt = dev_id; 1748d8bd7beSOleksij Rempel 1758d8bd7beSOleksij Rempel evt->event_handler(evt); 1768d8bd7beSOleksij Rempel 1778d8bd7beSOleksij Rempel writel_relaxed(BM_IR_MR0, priv.base + HW_IR); 1788d8bd7beSOleksij Rempel 1798d8bd7beSOleksij Rempel return IRQ_HANDLED; 1808d8bd7beSOleksij Rempel } 1818d8bd7beSOleksij Rempel 1828d8bd7beSOleksij Rempel /* 1838d8bd7beSOleksij Rempel * --------------------------------------------------------------------------- 1848d8bd7beSOleksij Rempel * Timer initialization 1858d8bd7beSOleksij Rempel * --------------------------------------------------------------------------- 1868d8bd7beSOleksij Rempel */ 187be5eb33dSDaniel Lezcano static int __init asm9260_timer_init(struct device_node *np) 1888d8bd7beSOleksij Rempel { 1898d8bd7beSOleksij Rempel int irq; 1908d8bd7beSOleksij Rempel struct clk *clk; 1918d8bd7beSOleksij Rempel int ret; 1928d8bd7beSOleksij Rempel unsigned long rate; 1938d8bd7beSOleksij Rempel 1948d8bd7beSOleksij Rempel priv.base = of_io_request_and_map(np, 0, np->name); 195be5eb33dSDaniel Lezcano if (IS_ERR(priv.base)) { 196ac9ce6d1SRafał Miłecki pr_err("%s: unable to map resource\n", np->name); 197be5eb33dSDaniel Lezcano return PTR_ERR(priv.base); 198be5eb33dSDaniel Lezcano } 1998d8bd7beSOleksij Rempel 2008d8bd7beSOleksij Rempel clk = of_clk_get(np, 0); 2018d8bd7beSOleksij Rempel 2028d8bd7beSOleksij Rempel ret = clk_prepare_enable(clk); 203be5eb33dSDaniel Lezcano if (ret) { 204be5eb33dSDaniel Lezcano pr_err("Failed to enable clk!\n"); 205be5eb33dSDaniel Lezcano return ret; 206be5eb33dSDaniel Lezcano } 2078d8bd7beSOleksij Rempel 2088d8bd7beSOleksij Rempel irq = irq_of_parse_and_map(np, 0); 2098d8bd7beSOleksij Rempel ret = request_irq(irq, asm9260_timer_interrupt, IRQF_TIMER, 2108d8bd7beSOleksij Rempel DRIVER_NAME, &event_dev); 211be5eb33dSDaniel Lezcano if (ret) { 212be5eb33dSDaniel Lezcano pr_err("Failed to setup irq!\n"); 213be5eb33dSDaniel Lezcano return ret; 214be5eb33dSDaniel Lezcano } 2158d8bd7beSOleksij Rempel 2168d8bd7beSOleksij Rempel /* set all timers for count-up */ 2178d8bd7beSOleksij Rempel writel_relaxed(BM_DIR_DEFAULT, priv.base + HW_DIR); 2188d8bd7beSOleksij Rempel /* disable divider */ 2198d8bd7beSOleksij Rempel writel_relaxed(BM_PR_DISABLE, priv.base + HW_PR); 2208d8bd7beSOleksij Rempel /* make sure all timers use every rising PCLK edge. */ 2218d8bd7beSOleksij Rempel writel_relaxed(BM_CTCR_DEFAULT, priv.base + HW_CTCR); 2228d8bd7beSOleksij Rempel /* enable interrupt for TC0 and clean setting for all other lines */ 2238d8bd7beSOleksij Rempel writel_relaxed(BM_MCR_INT_EN(0) , priv.base + HW_MCR); 2248d8bd7beSOleksij Rempel 2258d8bd7beSOleksij Rempel rate = clk_get_rate(clk); 2268d8bd7beSOleksij Rempel clocksource_mmio_init(priv.base + HW_TC1, DRIVER_NAME, rate, 2278d8bd7beSOleksij Rempel 200, 32, clocksource_mmio_readl_up); 2288d8bd7beSOleksij Rempel 2298d8bd7beSOleksij Rempel /* Seems like we can't use counter without match register even if 2308d8bd7beSOleksij Rempel * actions for MR are disabled. So, set MR to max value. */ 2318d8bd7beSOleksij Rempel writel_relaxed(0xffffffff, priv.base + HW_MR1); 2328d8bd7beSOleksij Rempel /* enable TC1 */ 2338d8bd7beSOleksij Rempel writel_relaxed(BM_C1_EN, priv.base + HW_TCR + SET_REG); 2348d8bd7beSOleksij Rempel 2358d8bd7beSOleksij Rempel priv.ticks_per_jiffy = DIV_ROUND_CLOSEST(rate, HZ); 2368d8bd7beSOleksij Rempel event_dev.cpumask = cpumask_of(0); 2378d8bd7beSOleksij Rempel clockevents_config_and_register(&event_dev, rate, 0x2c00, 0xfffffffe); 238be5eb33dSDaniel Lezcano 239be5eb33dSDaniel Lezcano return 0; 2408d8bd7beSOleksij Rempel } 241*17273395SDaniel Lezcano TIMER_OF_DECLARE(asm9260_timer, "alphascale,asm9260-timer", 2428d8bd7beSOleksij Rempel asm9260_timer_init); 243