Adds a driver for the periodic timer found on Ralink SoC. Signed-off-by: John Crispin <blogic@xxxxxxxxxxx> --- arch/mips/ralink/Makefile | 2 +- arch/mips/ralink/timer.c | 193 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 arch/mips/ralink/timer.c diff --git a/arch/mips/ralink/Makefile b/arch/mips/ralink/Makefile index 38cf1a8..e37e0ec 100644 --- a/arch/mips/ralink/Makefile +++ b/arch/mips/ralink/Makefile @@ -6,7 +6,7 @@ # Copyright (C) 2009-2011 Gabor Juhos <juhosg@xxxxxxxxxxx> # Copyright (C) 2013 John Crispin <blogic@xxxxxxxxxxx> -obj-y := prom.o of.o reset.o clk.o irq.o +obj-y := prom.o of.o reset.o clk.o irq.o timer.o obj-$(CONFIG_SOC_RT288X) += rt288x.o obj-$(CONFIG_SOC_RT305X) += rt305x.o diff --git a/arch/mips/ralink/timer.c b/arch/mips/ralink/timer.c new file mode 100644 index 0000000..55b4f9c --- /dev/null +++ b/arch/mips/ralink/timer.c @@ -0,0 +1,193 @@ +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * Copyright (C) 2013 John Crispin <blogic@xxxxxxxxxxx> +*/ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/timer.h> +#include <linux/of_gpio.h> +#include <linux/clk.h> + +#include <asm/mach-ralink/ralink_regs.h> + +#define TIMER_REG_TMRSTAT 0x00 +#define TIMER_REG_TMR0LOAD 0x10 +#define TIMER_REG_TMR0CTL 0x18 + +#define TMRSTAT_TMR0INT BIT(0) + +#define TMR0CTL_ENABLE BIT(7) +#define TMR0CTL_MODE_PERIODIC BIT(4) +#define TMR0CTL_PRESCALER 1 +#define TMR0CTL_PRESCALE_VAL (0xf - TMR0CTL_PRESCALER) +#define TMR0CTL_PRESCALE_DIV (65536 / BIT(TMR0CTL_PRESCALER)) + +struct rt_timer { + struct device *dev; + void __iomem *membase; + int irq; + unsigned long timer_freq; + unsigned long timer_div; +}; + +static inline void rt_timer_w32(struct rt_timer *rt, u8 reg, u32 val) +{ + __raw_writel(val, rt->membase + reg); +} + +static inline u32 rt_timer_r32(struct rt_timer *rt, u8 reg) +{ + return __raw_readl(rt->membase + reg); +} + +static irqreturn_t rt_timer_irq(int irq, void *_rt) +{ + struct rt_timer *rt = (struct rt_timer *) _rt; + + rt_timer_w32(rt, TIMER_REG_TMR0LOAD, rt->timer_freq / rt->timer_div); + rt_timer_w32(rt, TIMER_REG_TMRSTAT, TMRSTAT_TMR0INT); + + return IRQ_HANDLED; +} + + +static int rt_timer_request(struct rt_timer *rt) +{ + int err = devm_request_irq(rt->dev, rt->irq, rt_timer_irq, + 0, dev_name(rt->dev), rt); + + if (err) { + dev_err(rt->dev, "failed to request irq\n"); + } else { + u32 t = TMR0CTL_MODE_PERIODIC | TMR0CTL_PRESCALE_VAL; + rt_timer_w32(rt, TIMER_REG_TMR0CTL, t); + } + return err; +} + +static void rt_timer_free(struct rt_timer *rt) +{ + free_irq(rt->irq, rt); +} + +static int rt_timer_config(struct rt_timer *rt, unsigned long divisor) +{ + if (rt->timer_freq < divisor) + rt->timer_div = rt->timer_freq; + else + rt->timer_div = divisor; + + rt_timer_w32(rt, TIMER_REG_TMR0LOAD, rt->timer_freq / rt->timer_div); + + return 0; +} + +static int rt_timer_enable(struct rt_timer *rt) +{ + u32 t; + + rt_timer_w32(rt, TIMER_REG_TMR0LOAD, rt->timer_freq / rt->timer_div); + + t = rt_timer_r32(rt, TIMER_REG_TMR0CTL); + t |= TMR0CTL_ENABLE; + rt_timer_w32(rt, TIMER_REG_TMR0CTL, t); + + return 0; +} + +static void rt_timer_disable(struct rt_timer *rt) +{ + u32 t; + + t = rt_timer_r32(rt, TIMER_REG_TMR0CTL); + t &= ~TMR0CTL_ENABLE; + rt_timer_w32(rt, TIMER_REG_TMR0CTL, t); +} + +static int rt_timer_probe(struct platform_device *pdev) +{ + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + struct rt_timer *rt; + struct clk *clk; + + if (!res) { + dev_err(&pdev->dev, "no memory resource found\n"); + return -EINVAL; + } + + rt = devm_kzalloc(&pdev->dev, sizeof(*rt), GFP_KERNEL); + if (!rt) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + rt->irq = platform_get_irq(pdev, 0); + if (!rt->irq) { + dev_err(&pdev->dev, "failed to load irq\n"); + return -ENOENT; + } + + rt->membase = devm_ioremap_resource(&pdev->dev, res); + if (!rt->membase) { + dev_err(&pdev->dev, "failed to ioremap\n"); + return -ENOMEM; + } + + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "failed get clock rate\n"); + return PTR_ERR(clk); + } + + rt->timer_freq = clk_get_rate(clk) / TMR0CTL_PRESCALE_DIV; + if (!rt->timer_freq) + return -EINVAL; + + rt->dev = &pdev->dev; + platform_set_drvdata(pdev, rt); + + rt_timer_request(rt); + rt_timer_config(rt, 2); + rt_timer_enable(rt); + + dev_info(&pdev->dev, "maximum frequency is %luHz\n", rt->timer_freq); + + return 0; +} + +static int rt_timer_remove(struct platform_device *pdev) +{ + struct rt_timer *rt = platform_get_drvdata(pdev); + + rt_timer_disable(rt); + rt_timer_free(rt); + + return 0; +} + +static const struct of_device_id rt_timer_match[] = { + { .compatible = "ralink,rt2880-timer" }, + {}, +}; +MODULE_DEVICE_TABLE(of, rt_timer_match); + +static struct platform_driver rt_timer_driver = { + .probe = rt_timer_probe, + .remove = rt_timer_remove, + .driver = { + .name = "rt-timer", + .owner = THIS_MODULE, + .of_match_table = rt_timer_match + }, +}; + +module_platform_driver(rt_timer_driver); + +MODULE_DESCRIPTION("Ralink RT2880 timer"); +MODULE_AUTHOR("John Crispin <blogic@xxxxxxxxxxx"); +MODULE_LICENSE("GPL"); -- 1.7.10.4