diff options
Diffstat (limited to 'drivers/clocksource')
-rw-r--r-- | drivers/clocksource/Kconfig | 29 | ||||
-rw-r--r-- | drivers/clocksource/Makefile | 7 | ||||
-rw-r--r-- | drivers/clocksource/armv7m_systick.c | 79 | ||||
-rw-r--r-- | drivers/clocksource/asm9260_timer.c | 2 | ||||
-rw-r--r-- | drivers/clocksource/exynos_mct.c | 22 | ||||
-rw-r--r-- | drivers/clocksource/h8300_timer16.c | 254 | ||||
-rw-r--r-- | drivers/clocksource/h8300_timer8.c | 313 | ||||
-rw-r--r-- | drivers/clocksource/h8300_tpu.c | 207 | ||||
-rw-r--r-- | drivers/clocksource/qcom-timer.c | 59 | ||||
-rw-r--r-- | drivers/clocksource/time-lpc32xx.c | 272 | ||||
-rw-r--r-- | drivers/clocksource/timer-imx-gpt.c | 540 | ||||
-rw-r--r-- | drivers/clocksource/timer-integrator-ap.c | 2 | ||||
-rw-r--r-- | drivers/clocksource/timer-stm32.c | 184 | ||||
-rw-r--r-- | drivers/clocksource/timer-sun5i.c | 2 |
14 files changed, 1891 insertions, 81 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 51d7865fdddb..352b6a29910f 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -106,6 +106,16 @@ config CLKSRC_EFM32 Support to use the timers of EFM32 SoCs as clock source and clock event device. +config CLKSRC_LPC32XX + bool + select CLKSRC_MMIO + select CLKSRC_OF + +config CLKSRC_STM32 + bool "Clocksource for STM32 SoCs" if !ARCH_STM32 + depends on OF && ARM && (ARCH_STM32 || COMPILE_TEST) + select CLKSRC_MMIO + config ARM_ARCH_TIMER bool select CLKSRC_OF if OF @@ -139,6 +149,13 @@ config CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK help Use ARM global timer clock source as sched_clock +config ARMV7M_SYSTICK + bool + select CLKSRC_OF if OF + select CLKSRC_MMIO + help + This options enables support for the ARMv7M system timer unit + config ATMEL_PIT select CLKSRC_OF if OF def_bool SOC_AT91SAM9 || SOC_SAMA5 @@ -258,4 +275,16 @@ config CLKSRC_PXA help This enables OST0 support available on PXA and SA-11x0 platforms. + +config H8300_TMR16 + bool + +config H8300_TPU + bool + +config CLKSRC_IMX_GPT + bool "Clocksource using i.MX GPT" if COMPILE_TEST + depends on ARM && CLKDEV_LOOKUP + select CLKSRC_MMIO + endmenu diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 5b85f6adb258..e268b5e1901c 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -36,7 +36,9 @@ obj-$(CONFIG_ARCH_NSPIRE) += zevio-timer.o obj-$(CONFIG_ARCH_BCM_MOBILE) += bcm_kona_timer.o obj-$(CONFIG_CADENCE_TTC_TIMER) += cadence_ttc_timer.o obj-$(CONFIG_CLKSRC_EFM32) += time-efm32.o +obj-$(CONFIG_CLKSRC_STM32) += timer-stm32.o obj-$(CONFIG_CLKSRC_EXYNOS_MCT) += exynos_mct.o +obj-$(CONFIG_CLKSRC_LPC32XX) += time-lpc32xx.o obj-$(CONFIG_CLKSRC_SAMSUNG_PWM) += samsung_pwm_timer.o obj-$(CONFIG_FSL_FTM_TIMER) += fsl_ftm_timer.o obj-$(CONFIG_VF_PIT_TIMER) += vf_pit_timer.o @@ -45,10 +47,15 @@ obj-$(CONFIG_MTK_TIMER) += mtk_timer.o obj-$(CONFIG_ARM_ARCH_TIMER) += arm_arch_timer.o obj-$(CONFIG_ARM_GLOBAL_TIMER) += arm_global_timer.o +obj-$(CONFIG_ARMV7M_SYSTICK) += armv7m_systick.o obj-$(CONFIG_CLKSRC_METAG_GENERIC) += metag_generic.o obj-$(CONFIG_ARCH_HAS_TICK_BROADCAST) += dummy_timer.o obj-$(CONFIG_ARCH_KEYSTONE) += timer-keystone.o obj-$(CONFIG_ARCH_INTEGRATOR_AP) += timer-integrator-ap.o obj-$(CONFIG_CLKSRC_VERSATILE) += versatile.o obj-$(CONFIG_CLKSRC_MIPS_GIC) += mips-gic-timer.o +obj-$(CONFIG_CLKSRC_IMX_GPT) += timer-imx-gpt.o obj-$(CONFIG_ASM9260_TIMER) += asm9260_timer.o +obj-$(CONFIG_H8300) += h8300_timer8.o +obj-$(CONFIG_H8300_TMR16) += h8300_timer16.o +obj-$(CONFIG_H8300_TPU) += h8300_tpu.o diff --git a/drivers/clocksource/armv7m_systick.c b/drivers/clocksource/armv7m_systick.c new file mode 100644 index 000000000000..addfd2c64f54 --- /dev/null +++ b/drivers/clocksource/armv7m_systick.c @@ -0,0 +1,79 @@ +/* + * Copyright (C) Maxime Coquelin 2015 + * Author: Maxime Coquelin <mcoquelin.stm32@gmail.com> + * License terms: GNU General Public License (GPL), version 2 + */ + +#include <linux/kernel.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/clk.h> +#include <linux/bitops.h> + +#define SYST_CSR 0x00 +#define SYST_RVR 0x04 +#define SYST_CVR 0x08 +#define SYST_CALIB 0x0c + +#define SYST_CSR_ENABLE BIT(0) + +#define SYSTICK_LOAD_RELOAD_MASK 0x00FFFFFF + +static void __init system_timer_of_register(struct device_node *np) +{ + struct clk *clk = NULL; + void __iomem *base; + u32 rate; + int ret; + + base = of_iomap(np, 0); + if (!base) { + pr_warn("system-timer: invalid base address\n"); + return; + } + + ret = of_property_read_u32(np, "clock-frequency", &rate); + if (ret) { + clk = of_clk_get(np, 0); + if (IS_ERR(clk)) + goto out_unmap; + + ret = clk_prepare_enable(clk); + if (ret) + goto out_clk_put; + + rate = clk_get_rate(clk); + if (!rate) + goto out_clk_disable; + } + + writel_relaxed(SYSTICK_LOAD_RELOAD_MASK, base + SYST_RVR); + writel_relaxed(SYST_CSR_ENABLE, base + SYST_CSR); + + ret = clocksource_mmio_init(base + SYST_CVR, "arm_system_timer", rate, + 200, 24, clocksource_mmio_readl_down); + if (ret) { + pr_err("failed to init clocksource (%d)\n", ret); + if (clk) + goto out_clk_disable; + else + goto out_unmap; + } + + pr_info("ARM System timer initialized as clocksource\n"); + + return; + +out_clk_disable: + clk_disable_unprepare(clk); +out_clk_put: + clk_put(clk); +out_unmap: + iounmap(base); + pr_warn("ARM System timer register failed (%d)\n", ret); +} + +CLOCKSOURCE_OF_DECLARE(arm_systick, "arm,armv7m-systick", + system_timer_of_register); diff --git a/drivers/clocksource/asm9260_timer.c b/drivers/clocksource/asm9260_timer.c index 2c9c993727c8..4c2ba59897e8 100644 --- a/drivers/clocksource/asm9260_timer.c +++ b/drivers/clocksource/asm9260_timer.c @@ -178,7 +178,7 @@ static void __init asm9260_timer_init(struct device_node *np) unsigned long rate; priv.base = of_io_request_and_map(np, 0, np->name); - if (!priv.base) + if (IS_ERR(priv.base)) panic("%s: unable to map resource", np->name); clk = of_clk_get(np, 0); diff --git a/drivers/clocksource/exynos_mct.c b/drivers/clocksource/exynos_mct.c index 83564c9cfdbe..935b05936dbd 100644 --- a/drivers/clocksource/exynos_mct.c +++ b/drivers/clocksource/exynos_mct.c @@ -209,7 +209,7 @@ static void exynos4_frc_resume(struct clocksource *cs) exynos4_mct_frc_start(); } -struct clocksource mct_frc = { +static struct clocksource mct_frc = { .name = "mct-frc", .rating = 400, .read = exynos4_frc_read, @@ -413,7 +413,7 @@ static inline void exynos4_tick_set_mode(enum clock_event_mode mode, } } -static int exynos4_mct_tick_clear(struct mct_clock_event_device *mevt) +static void exynos4_mct_tick_clear(struct mct_clock_event_device *mevt) { struct clock_event_device *evt = &mevt->evt; @@ -426,12 +426,8 @@ static int exynos4_mct_tick_clear(struct mct_clock_event_device *mevt) exynos4_mct_tick_stop(mevt); /* Clear the MCT tick interrupt */ - if (readl_relaxed(reg_base + mevt->base + MCT_L_INT_CSTAT_OFFSET) & 1) { + if (readl_relaxed(reg_base + mevt->base + MCT_L_INT_CSTAT_OFFSET) & 1) exynos4_mct_write(0x1, mevt->base + MCT_L_INT_CSTAT_OFFSET); - return 1; - } else { - return 0; - } } static irqreturn_t exynos4_mct_tick_isr(int irq, void *dev_id) @@ -564,18 +560,6 @@ out_irq: free_percpu_irq(mct_irqs[MCT_L0_IRQ], &percpu_mct_tick); } -void __init mct_init(void __iomem *base, int irq_g0, int irq_l0, int irq_l1) -{ - mct_irqs[MCT_G0_IRQ] = irq_g0; - mct_irqs[MCT_L0_IRQ] = irq_l0; - mct_irqs[MCT_L1_IRQ] = irq_l1; - mct_int_type = MCT_INT_SPI; - - exynos4_timer_resources(NULL, base); - exynos4_clocksource_init(); - exynos4_clockevent_init(); -} - static void __init mct_init_dt(struct device_node *np, unsigned int int_type) { u32 nr_irqs, i; diff --git a/drivers/clocksource/h8300_timer16.c b/drivers/clocksource/h8300_timer16.c new file mode 100644 index 000000000000..82941c1e9e33 --- /dev/null +++ b/drivers/clocksource/h8300_timer16.c @@ -0,0 +1,254 @@ +/* + * H8/300 16bit Timer driver + * + * Copyright 2015 Yoshinori Sato <ysato@users.sourcefoge.jp> + */ + +#include <linux/errno.h> +#include <linux/kernel.h> +#include <linux/param.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clocksource.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> + +#include <asm/segment.h> +#include <asm/irq.h> + +#define TSTR 0 +#define TSNC 1 +#define TMDR 2 +#define TOLR 3 +#define TISRA 4 +#define TISRB 5 +#define TISRC 6 + +#define TCR 0 +#define TIOR 1 +#define TCNT 2 +#define GRA 4 +#define GRB 6 + +#define FLAG_REPROGRAM (1 << 0) +#define FLAG_SKIPEVENT (1 << 1) +#define FLAG_IRQCONTEXT (1 << 2) +#define FLAG_STARTED (1 << 3) + +#define ONESHOT 0 +#define PERIODIC 1 + +#define RELATIVE 0 +#define ABSOLUTE 1 + +struct timer16_priv { + struct platform_device *pdev; + struct clocksource cs; + struct irqaction irqaction; + unsigned long total_cycles; + unsigned long mapbase; + unsigned long mapcommon; + unsigned long flags; + unsigned short gra; + unsigned short cs_enabled; + unsigned char enb; + unsigned char imfa; + unsigned char imiea; + unsigned char ovf; + raw_spinlock_t lock; + struct clk *clk; +}; + +static unsigned long timer16_get_counter(struct timer16_priv *p) +{ + unsigned long v1, v2, v3; + int o1, o2; + + o1 = ctrl_inb(p->mapcommon + TISRC) & p->ovf; + + /* Make sure the timer value is stable. Stolen from acpi_pm.c */ + do { + o2 = o1; + v1 = ctrl_inw(p->mapbase + TCNT); + v2 = ctrl_inw(p->mapbase + TCNT); + v3 = ctrl_inw(p->mapbase + TCNT); + o1 = ctrl_inb(p->mapcommon + TISRC) & p->ovf; + } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3) + || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2))); + + v2 |= 0x10000; + return v2; +} + + +static irqreturn_t timer16_interrupt(int irq, void *dev_id) +{ + struct timer16_priv *p = (struct timer16_priv *)dev_id; + + ctrl_outb(ctrl_inb(p->mapcommon + TISRA) & ~p->imfa, + p->mapcommon + TISRA); + p->total_cycles += 0x10000; + + return IRQ_HANDLED; +} + +static inline struct timer16_priv *cs_to_priv(struct clocksource *cs) +{ + return container_of(cs, struct timer16_priv, cs); +} + +static cycle_t timer16_clocksource_read(struct clocksource *cs) +{ + struct timer16_priv *p = cs_to_priv(cs); + unsigned long flags, raw; + unsigned long value; + + raw_spin_lock_irqsave(&p->lock, flags); + value = p->total_cycles; + raw = timer16_get_counter(p); + raw_spin_unlock_irqrestore(&p->lock, flags); + + return value + raw; +} + +static int timer16_enable(struct clocksource *cs) +{ + struct timer16_priv *p = cs_to_priv(cs); + + WARN_ON(p->cs_enabled); + + p->total_cycles = 0; + ctrl_outw(0x0000, p->mapbase + TCNT); + ctrl_outb(0x83, p->mapbase + TCR); + ctrl_outb(ctrl_inb(p->mapcommon + TSTR) | p->enb, + p->mapcommon + TSTR); + + p->cs_enabled = true; + return 0; +} + +static void timer16_disable(struct clocksource *cs) +{ + struct timer16_priv *p = cs_to_priv(cs); + + WARN_ON(!p->cs_enabled); + + ctrl_outb(ctrl_inb(p->mapcommon + TSTR) & ~p->enb, + p->mapcommon + TSTR); + + p->cs_enabled = false; +} + +#define REG_CH 0 +#define REG_COMM 1 + +static int timer16_setup(struct timer16_priv *p, struct platform_device *pdev) +{ + struct resource *res[2]; + int ret, irq; + unsigned int ch; + + memset(p, 0, sizeof(*p)); + p->pdev = pdev; + + res[REG_CH] = platform_get_resource(p->pdev, + IORESOURCE_MEM, REG_CH); + res[REG_COMM] = platform_get_resource(p->pdev, + IORESOURCE_MEM, REG_COMM); + if (!res[REG_CH] || !res[REG_COMM]) { + dev_err(&p->pdev->dev, "failed to get I/O memory\n"); + return -ENXIO; + } + irq = platform_get_irq(p->pdev, 0); + if (irq < 0) { + dev_err(&p->pdev->dev, "failed to get irq\n"); + return irq; + } + + p->clk = clk_get(&p->pdev->dev, "fck"); + if (IS_ERR(p->clk)) { + dev_err(&p->pdev->dev, "can't get clk\n"); + return PTR_ERR(p->clk); + } + of_property_read_u32(p->pdev->dev.of_node, "renesas,channel", &ch); + + p->pdev = pdev; + p->mapbase = res[REG_CH]->start; + p->mapcommon = res[REG_COMM]->start; + p->enb = 1 << ch; + p->imfa = 1 << ch; + p->imiea = 1 << (4 + ch); + p->cs.name = pdev->name; + p->cs.rating = 200; + p->cs.read = timer16_clocksource_read; + p->cs.enable = timer16_enable; + p->cs.disable = timer16_disable; + p->cs.mask = CLOCKSOURCE_MASK(sizeof(unsigned long) * 8); + p->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS; + + ret = request_irq(irq, timer16_interrupt, + IRQF_TIMER, pdev->name, p); + if (ret < 0) { + dev_err(&p->pdev->dev, "failed to request irq %d\n", irq); + return ret; + } + + clocksource_register_hz(&p->cs, clk_get_rate(p->clk) / 8); + + return 0; +} + +static int timer16_probe(struct platform_device *pdev) +{ + struct timer16_priv *p = platform_get_drvdata(pdev); + + if (p) { + dev_info(&pdev->dev, "kept as earlytimer\n"); + return 0; + } + + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + return timer16_setup(p, pdev); +} + +static int timer16_remove(struct platform_device *pdev) +{ + return -EBUSY; +} + +static const struct of_device_id timer16_of_table[] = { + { .compatible = "renesas,16bit-timer" }, + { } +}; +static struct platform_driver timer16_driver = { + .probe = timer16_probe, + .remove = timer16_remove, + .driver = { + .name = "h8300h-16timer", + .of_match_table = of_match_ptr(timer16_of_table), + } +}; + +static int __init timer16_init(void) +{ + return platform_driver_register(&timer16_driver); +} + +static void __exit timer16_exit(void) +{ + platform_driver_unregister(&timer16_driver); +} + +subsys_initcall(timer16_init); +module_exit(timer16_exit); +MODULE_AUTHOR("Yoshinori Sato"); +MODULE_DESCRIPTION("H8/300H 16bit Timer Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/h8300_timer8.c b/drivers/clocksource/h8300_timer8.c new file mode 100644 index 000000000000..0214cb3a7f5e --- /dev/null +++ b/drivers/clocksource/h8300_timer8.c @@ -0,0 +1,313 @@ +/* + * linux/arch/h8300/kernel/cpu/timer/timer8.c + * + * Yoshinori Sato <ysato@users.sourcefoge.jp> + * + * 8bit Timer driver + * + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clockchips.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> + +#include <asm/irq.h> + +#define _8TCR 0 +#define _8TCSR 2 +#define TCORA 4 +#define TCORB 6 +#define _8TCNT 8 + +#define FLAG_REPROGRAM (1 << 0) +#define FLAG_SKIPEVENT (1 << 1) +#define FLAG_IRQCONTEXT (1 << 2) +#define FLAG_STARTED (1 << 3) + +#define ONESHOT 0 +#define PERIODIC 1 + +#define RELATIVE 0 +#define ABSOLUTE 1 + +struct timer8_priv { + struct platform_device *pdev; + struct clock_event_device ced; + struct irqaction irqaction; + unsigned long mapbase; + raw_spinlock_t lock; + unsigned long flags; + unsigned int rate; + unsigned int tcora; + struct clk *pclk; +}; + +static unsigned long timer8_get_counter(struct timer8_priv *p) +{ + unsigned long v1, v2, v3; + int o1, o2; + + o1 = ctrl_inb(p->mapbase + _8TCSR) & 0x20; + + /* Make sure the timer value is stable. Stolen from acpi_pm.c */ + do { + o2 = o1; + v1 = ctrl_inw(p->mapbase + _8TCNT); + v2 = ctrl_inw(p->mapbase + _8TCNT); + v3 = ctrl_inw(p->mapbase + _8TCNT); + o1 = ctrl_inb(p->mapbase + _8TCSR) & 0x20; + } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3) + || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2))); + + v2 |= o1 << 10; + return v2; +} + +static irqreturn_t timer8_interrupt(int irq, void *dev_id) +{ + struct timer8_priv *p = dev_id; + + ctrl_outb(ctrl_inb(p->mapbase + _8TCSR) & ~0x40, + p->mapbase + _8TCSR); + p->flags |= FLAG_IRQCONTEXT; + ctrl_outw(p->tcora, p->mapbase + TCORA); + if (!(p->flags & FLAG_SKIPEVENT)) { + if (p->ced.mode == CLOCK_EVT_MODE_ONESHOT) + ctrl_outw(0x0000, p->mapbase + _8TCR); + p->ced.event_handler(&p->ced); + } + p->flags &= ~(FLAG_SKIPEVENT | FLAG_IRQCONTEXT); + + return IRQ_HANDLED; +} + +static void timer8_set_next(struct timer8_priv *p, unsigned long delta) +{ + unsigned long flags; + unsigned long now; + + raw_spin_lock_irqsave(&p->lock, flags); + if (delta >= 0x10000) + dev_warn(&p->pdev->dev, "delta out of range\n"); + now = timer8_get_counter(p); + p->tcora = delta; + ctrl_outb(ctrl_inb(p->mapbase + _8TCR) | 0x40, p->mapbase + _8TCR); + if (delta > now) + ctrl_outw(delta, p->mapbase + TCORA); + else + ctrl_outw(now + 1, p->mapbase + TCORA); + + raw_spin_unlock_irqrestore(&p->lock, flags); +} + +static int timer8_enable(struct timer8_priv *p) +{ + p->rate = clk_get_rate(p->pclk) / 64; + ctrl_outw(0xffff, p->mapbase + TCORA); + ctrl_outw(0x0000, p->mapbase + _8TCNT); + ctrl_outw(0x0c02, p->mapbase + _8TCR); + + return 0; +} + +static int timer8_start(struct timer8_priv *p) +{ + int ret = 0; + unsigned long flags; + + raw_spin_lock_irqsave(&p->lock, flags); + + if (!(p->flags & FLAG_STARTED)) + ret = timer8_enable(p); + + if (ret) + goto out; + p->flags |= FLAG_STARTED; + + out: + raw_spin_unlock_irqrestore(&p->lock, flags); + + return ret; +} + +static void timer8_stop(struct timer8_priv *p) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&p->lock, flags); + + ctrl_outw(0x0000, p->mapbase + _8TCR); + + raw_spin_unlock_irqrestore(&p->lock, flags); +} + +static inline struct timer8_priv *ced_to_priv(struct clock_event_device *ced) +{ + return container_of(ced, struct timer8_priv, ced); +} + +static void timer8_clock_event_start(struct timer8_priv *p, int periodic) +{ + struct clock_event_device *ced = &p->ced; + + timer8_start(p); + + ced->shift = 32; + ced->mult = div_sc(p->rate, NSEC_PER_SEC, ced->shift); + ced->max_delta_ns = clockevent_delta2ns(0xffff, ced); + ced->min_delta_ns = clockevent_delta2ns(0x0001, ced); + + timer8_set_next(p, periodic?(p->rate + HZ/2) / HZ:0x10000); +} + +static void timer8_clock_event_mode(enum clock_event_mode mode, + struct clock_event_device *ced) +{ + struct timer8_priv *p = ced_to_priv(ced); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + dev_info(&p->pdev->dev, "used for periodic clock events\n"); + timer8_stop(p); + timer8_clock_event_start(p, PERIODIC); + break; + case CLOCK_EVT_MODE_ONESHOT: + dev_info(&p->pdev->dev, "used for oneshot clock events\n"); + timer8_stop(p); + timer8_clock_event_start(p, ONESHOT); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + timer8_stop(p); + break; + default: + break; + } +} + +static int timer8_clock_event_next(unsigned long delta, + struct clock_event_device *ced) +{ + struct timer8_priv *p = ced_to_priv(ced); + + BUG_ON(ced->mode != CLOCK_EVT_MODE_ONESHOT); + timer8_set_next(p, delta - 1); + + return 0; +} + +static int timer8_setup(struct timer8_priv *p, + struct platform_device *pdev) +{ + struct resource *res; + int irq; + int ret; + + memset(p, 0, sizeof(*p)); + p->pdev = pdev; + + res = platform_get_resource(p->pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&p->pdev->dev, "failed to get I/O memory\n"); + return -ENXIO; + } + + irq = platform_get_irq(p->pdev, 0); + if (irq < 0) { + dev_err(&p->pdev->dev, "failed to get irq\n"); + return -ENXIO; + } + + p->mapbase = res->start; + + p->irqaction.name = dev_name(&p->pdev->dev); + p->irqaction.handler = timer8_interrupt; + p->irqaction.dev_id = p; + p->irqaction.flags = IRQF_TIMER; + + p->pclk = clk_get(&p->pdev->dev, "fck"); + if (IS_ERR(p->pclk)) { + dev_err(&p->pdev->dev, "can't get clk\n"); + return PTR_ERR(p->pclk); + } + + p->ced.name = pdev->name; + p->ced.features = CLOCK_EVT_FEAT_PERIODIC | + CLOCK_EVT_FEAT_ONESHOT; + p->ced.rating = 200; + p->ced.cpumask = cpumask_of(0); + p->ced.set_next_event = timer8_clock_event_next; + p->ced.set_mode = timer8_clock_event_mode; + + ret = setup_irq(irq, &p->irqaction); + if (ret < 0) { + dev_err(&p->pdev->dev, + "failed to request irq %d\n", irq); + return ret; + } + clockevents_register_device(&p->ced); + platform_set_drvdata(pdev, p); + + return 0; +} + +static int timer8_probe(struct platform_device *pdev) +{ + struct timer8_priv *p = platform_get_drvdata(pdev); + + if (p) { + dev_info(&pdev->dev, "kept as earlytimer\n"); + return 0; + } + + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + return timer8_setup(p, pdev); +} + +static int timer8_remove(struct platform_device *pdev) +{ + return -EBUSY; +} + +static const struct of_device_id timer8_of_table[] __maybe_unused = { + { .compatible = "renesas,8bit-timer" }, + { } +}; + +MODULE_DEVICE_TABLE(of, timer8_of_table); +static struct platform_driver timer8_driver = { + .probe = timer8_probe, + .remove = timer8_remove, + .driver = { + .name = "h8300-8timer", + .of_match_table = of_match_ptr(timer8_of_table), + } +}; + +static int __init timer8_init(void) +{ + return platform_driver_register(&timer8_driver); +} + +static void __exit timer8_exit(void) +{ + platform_driver_unregister(&timer8_driver); +} + +subsys_initcall(timer8_init); +module_exit(timer8_exit); +MODULE_AUTHOR("Yoshinori Sato"); +MODULE_DESCRIPTION("H8/300 8bit Timer Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/h8300_tpu.c b/drivers/clocksource/h8300_tpu.c new file mode 100644 index 000000000000..64195fdd78bf --- /dev/null +++ b/drivers/clocksource/h8300_tpu.c @@ -0,0 +1,207 @@ +/* + * H8/300 TPU Driver + * + * Copyright 2015 Yoshinori Sato <ysato@users.sourcefoge.jp> + * + */ + +#include <linux/errno.h> +#include <linux/sched.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/clocksource.h> +#include <linux/module.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/of.h> + +#include <asm/irq.h> + +#define TCR 0 +#define TMDR 1 +#define TIOR 2 +#define TER 4 +#define TSR 5 +#define TCNT 6 +#define TGRA 8 +#define TGRB 10 +#define TGRC 12 +#define TGRD 14 + +struct tpu_priv { + struct platform_device *pdev; + struct clocksource cs; + struct clk *clk; + unsigned long mapbase1; + unsigned long mapbase2; + raw_spinlock_t lock; + unsigned int cs_enabled; +}; + +static inline unsigned long read_tcnt32(struct tpu_priv *p) +{ + unsigned long tcnt; + + tcnt = ctrl_inw(p->mapbase1 + TCNT) << 16; + tcnt |= ctrl_inw(p->mapbase2 + TCNT); + return tcnt; +} + +static int tpu_get_counter(struct tpu_priv *p, unsigned long long *val) +{ + unsigned long v1, v2, v3; + int o1, o2; + + o1 = ctrl_inb(p->mapbase1 + TSR) & 0x10; + + /* Make sure the timer value is stable. Stolen from acpi_pm.c */ + do { + o2 = o1; + v1 = read_tcnt32(p); + v2 = read_tcnt32(p); + v3 = read_tcnt32(p); + o1 = ctrl_inb(p->mapbase1 + TSR) & 0x10; + } while (unlikely((o1 != o2) || (v1 > v2 && v1 < v3) + || (v2 > v3 && v2 < v1) || (v3 > v1 && v3 < v2))); + + *val = v2; + return o1; +} + +static inline struct tpu_priv *cs_to_priv(struct clocksource *cs) +{ + return container_of(cs, struct tpu_priv, cs); +} + +static cycle_t tpu_clocksource_read(struct clocksource *cs) +{ + struct tpu_priv *p = cs_to_priv(cs); + unsigned long flags; + unsigned long long value; + + raw_spin_lock_irqsave(&p->lock, flags); + if (tpu_get_counter(p, &value)) + value += 0x100000000; + raw_spin_unlock_irqrestore(&p->lock, flags); + + return value; +} + +static int tpu_clocksource_enable(struct clocksource *cs) +{ + struct tpu_priv *p = cs_to_priv(cs); + + WARN_ON(p->cs_enabled); + + ctrl_outw(0, p->mapbase1 + TCNT); + ctrl_outw(0, p->mapbase2 + TCNT); + ctrl_outb(0x0f, p->mapbase1 + TCR); + ctrl_outb(0x03, p->mapbase2 + TCR); + + p->cs_enabled = true; + return 0; +} + +static void tpu_clocksource_disable(struct clocksource *cs) +{ + struct tpu_priv *p = cs_to_priv(cs); + + WARN_ON(!p->cs_enabled); + + ctrl_outb(0, p->mapbase1 + TCR); + ctrl_outb(0, p->mapbase2 + TCR); + p->cs_enabled = false; +} + +#define CH_L 0 +#define CH_H 1 + +static int __init tpu_setup(struct tpu_priv *p, struct platform_device *pdev) +{ + struct resource *res[2]; + + memset(p, 0, sizeof(*p)); + p->pdev = pdev; + + res[CH_L] = platform_get_resource(p->pdev, IORESOURCE_MEM, CH_L); + res[CH_H] = platform_get_resource(p->pdev, IORESOURCE_MEM, CH_H); + if (!res[CH_L] || !res[CH_H]) { + dev_err(&p->pdev->dev, "failed to get I/O memory\n"); + return -ENXIO; + } + + p->clk = clk_get(&p->pdev->dev, "fck"); + if (IS_ERR(p->clk)) { + dev_err(&p->pdev->dev, "can't get clk\n"); + return PTR_ERR(p->clk); + } + + p->mapbase1 = res[CH_L]->start; + p->mapbase2 = res[CH_H]->start; + + p->cs.name = pdev->name; + p->cs.rating = 200; + p->cs.read = tpu_clocksource_read; + p->cs.enable = tpu_clocksource_enable; + p->cs.disable = tpu_clocksource_disable; + p->cs.mask = CLOCKSOURCE_MASK(sizeof(unsigned long) * 8); + p->cs.flags = CLOCK_SOURCE_IS_CONTINUOUS; + clocksource_register_hz(&p->cs, clk_get_rate(p->clk) / 64); + platform_set_drvdata(pdev, p); + + return 0; +} + +static int tpu_probe(struct platform_device *pdev) +{ + struct tpu_priv *p = platform_get_drvdata(pdev); + + if (p) { + dev_info(&pdev->dev, "kept as earlytimer\n"); + return 0; + } + + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + return tpu_setup(p, pdev); +} + +static int tpu_remove(struct platform_device *pdev) +{ + return -EBUSY; +} + +static const struct of_device_id tpu_of_table[] = { + { .compatible = "renesas,tpu" }, + { } +}; + +static struct platform_driver tpu_driver = { + .probe = tpu_probe, + .remove = tpu_remove, + .driver = { + .name = "h8s-tpu", + .of_match_table = of_match_ptr(tpu_of_table), + } +}; + +static int __init tpu_init(void) +{ + return platform_driver_register(&tpu_driver); +} + +static void __exit tpu_exit(void) +{ + platform_driver_unregister(&tpu_driver); +} + +subsys_initcall(tpu_init); +module_exit(tpu_exit); +MODULE_AUTHOR("Yoshinori Sato"); +MODULE_DESCRIPTION("H8S Timer Pulse Unit Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/clocksource/qcom-timer.c b/drivers/clocksource/qcom-timer.c index 098c542e5c53..cba2d015564c 100644 --- a/drivers/clocksource/qcom-timer.c +++ b/drivers/clocksource/qcom-timer.c @@ -40,8 +40,6 @@ #define GPT_HZ 32768 -#define MSM_DGT_SHIFT 5 - static void __iomem *event_base; static void __iomem *sts_base; @@ -232,7 +230,6 @@ err: register_current_timer_delay(&msm_delay_timer); } -#ifdef CONFIG_ARCH_QCOM static void __init msm_dt_timer_init(struct device_node *np) { u32 freq; @@ -285,59 +282,3 @@ static void __init msm_dt_timer_init(struct device_node *np) } CLOCKSOURCE_OF_DECLARE(kpss_timer, "qcom,kpss-timer", msm_dt_timer_init); CLOCKSOURCE_OF_DECLARE(scss_timer, "qcom,scss-timer", msm_dt_timer_init); -#else - -static int __init msm_timer_map(phys_addr_t addr, u32 event, u32 source, - u32 sts) -{ - void __iomem *base; - - base = ioremap(addr, SZ_256); - if (!base) { - pr_err("Failed to map timer base\n"); - return -ENOMEM; - } - event_base = base + event; - source_base = base + source; - if (sts) - sts_base = base + sts; - - return 0; -} - -static notrace cycle_t msm_read_timer_count_shift(struct clocksource *cs) -{ - /* - * Shift timer count down by a constant due to unreliable lower bits - * on some targets. - */ - return msm_read_timer_count(cs) >> MSM_DGT_SHIFT; -} - -void __init msm7x01_timer_init(void) -{ - struct clocksource *cs = &msm_clocksource; - - if (msm_timer_map(0xc0100000, 0x0, 0x10, 0x0)) - return; - cs->read = msm_read_timer_count_shift; - cs->mask = CLOCKSOURCE_MASK((32 - MSM_DGT_SHIFT)); - /* 600 KHz */ - msm_timer_init(19200000 >> MSM_DGT_SHIFT, 32 - MSM_DGT_SHIFT, 7, - false); -} - -void __init msm7x30_timer_init(void) -{ - if (msm_timer_map(0xc0100000, 0x4, 0x24, 0x80)) - return; - msm_timer_init(24576000 / 4, 32, 1, false); -} - -void __init qsd8x50_timer_init(void) -{ - if (msm_timer_map(0xAC100000, 0x0, 0x10, 0x34)) - return; - msm_timer_init(19200000 / 4, 32, 7, false); -} -#endif diff --git a/drivers/clocksource/time-lpc32xx.c b/drivers/clocksource/time-lpc32xx.c new file mode 100644 index 000000000000..a1c06a2bc77c --- /dev/null +++ b/drivers/clocksource/time-lpc32xx.c @@ -0,0 +1,272 @@ +/* + * Clocksource driver for NXP LPC32xx/18xx/43xx timer + * + * Copyright (C) 2015 Joachim Eastwood <manabian@gmail.com> + * + * Based on: + * time-efm32 Copyright (C) 2013 Pengutronix + * mach-lpc32xx/timer.c Copyright (C) 2009 - 2010 NXP Semiconductors + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/sched_clock.h> + +#define LPC32XX_TIMER_IR 0x000 +#define LPC32XX_TIMER_IR_MR0INT BIT(0) +#define LPC32XX_TIMER_TCR 0x004 +#define LPC32XX_TIMER_TCR_CEN BIT(0) +#define LPC32XX_TIMER_TCR_CRST BIT(1) +#define LPC32XX_TIMER_TC 0x008 +#define LPC32XX_TIMER_PR 0x00c +#define LPC32XX_TIMER_MCR 0x014 +#define LPC32XX_TIMER_MCR_MR0I BIT(0) +#define LPC32XX_TIMER_MCR_MR0R BIT(1) +#define LPC32XX_TIMER_MCR_MR0S BIT(2) +#define LPC32XX_TIMER_MR0 0x018 +#define LPC32XX_TIMER_CTCR 0x070 + +struct lpc32xx_clock_event_ddata { + struct clock_event_device evtdev; + void __iomem *base; +}; + +/* Needed for the sched clock */ +static void __iomem *clocksource_timer_counter; + +static u64 notrace lpc32xx_read_sched_clock(void) +{ + return readl(clocksource_timer_counter); +} + +static int lpc32xx_clkevt_next_event(unsigned long delta, + struct clock_event_device *evtdev) +{ + struct lpc32xx_clock_event_ddata *ddata = + container_of(evtdev, struct lpc32xx_clock_event_ddata, evtdev); + + /* + * Place timer in reset and program the delta in the prescale + * register (PR). When the prescale counter matches the value + * in PR the counter register is incremented and the compare + * match will trigger. After setup the timer is released from + * reset and enabled. + */ + writel_relaxed(LPC32XX_TIMER_TCR_CRST, ddata->base + LPC32XX_TIMER_TCR); + writel_relaxed(delta, ddata->base + LPC32XX_TIMER_PR); + writel_relaxed(LPC32XX_TIMER_TCR_CEN, ddata->base + LPC32XX_TIMER_TCR); + + return 0; +} + +static int lpc32xx_clkevt_shutdown(struct clock_event_device *evtdev) +{ + struct lpc32xx_clock_event_ddata *ddata = + container_of(evtdev, struct lpc32xx_clock_event_ddata, evtdev); + + /* Disable the timer */ + writel_relaxed(0, ddata->base + LPC32XX_TIMER_TCR); + + return 0; +} + +static int lpc32xx_clkevt_oneshot(struct clock_event_device *evtdev) +{ + /* + * When using oneshot, we must also disable the timer + * to wait for the first call to set_next_event(). + */ + return lpc32xx_clkevt_shutdown(evtdev); +} + +static irqreturn_t lpc32xx_clock_event_handler(int irq, void *dev_id) +{ + struct lpc32xx_clock_event_ddata *ddata = dev_id; + + /* Clear match on channel 0 */ + writel_relaxed(LPC32XX_TIMER_IR_MR0INT, ddata->base + LPC32XX_TIMER_IR); + + ddata->evtdev.event_handler(&ddata->evtdev); + + return IRQ_HANDLED; +} + +static struct lpc32xx_clock_event_ddata lpc32xx_clk_event_ddata = { + .evtdev = { + .name = "lpc3220 clockevent", + .features = CLOCK_EVT_FEAT_ONESHOT, + .rating = 300, + .set_next_event = lpc32xx_clkevt_next_event, + .set_state_shutdown = lpc32xx_clkevt_shutdown, + .set_state_oneshot = lpc32xx_clkevt_oneshot, + }, +}; + +static int __init lpc32xx_clocksource_init(struct device_node *np) +{ + void __iomem *base; + unsigned long rate; + struct clk *clk; + int ret; + + clk = of_clk_get_by_name(np, "timerclk"); + if (IS_ERR(clk)) { + pr_err("clock get failed (%lu)\n", PTR_ERR(clk)); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret) { + pr_err("clock enable failed (%d)\n", ret); + goto err_clk_enable; + } + + base = of_iomap(np, 0); + if (!base) { + pr_err("unable to map registers\n"); + ret = -EADDRNOTAVAIL; + goto err_iomap; + } + + /* + * Disable and reset timer then set it to free running timer + * mode (CTCR) with no prescaler (PR) or match operations (MCR). + * After setup the timer is released from reset and enabled. + */ + writel_relaxed(LPC32XX_TIMER_TCR_CRST, base + LPC32XX_TIMER_TCR); + writel_relaxed(0, base + LPC32XX_TIMER_PR); + writel_relaxed(0, base + LPC32XX_TIMER_MCR); + writel_relaxed(0, base + LPC32XX_TIMER_CTCR); + writel_relaxed(LPC32XX_TIMER_TCR_CEN, base + LPC32XX_TIMER_TCR); + + rate = clk_get_rate(clk); + ret = clocksource_mmio_init(base + LPC32XX_TIMER_TC, "lpc3220 timer", + rate, 300, 32, clocksource_mmio_readl_up); + if (ret) { + pr_err("failed to init clocksource (%d)\n", ret); + goto err_clocksource_init; + } + + clocksource_timer_counter = base + LPC32XX_TIMER_TC; + sched_clock_register(lpc32xx_read_sched_clock, 32, rate); + + return 0; + +err_clocksource_init: + iounmap(base); +err_iomap: + clk_disable_unprepare(clk); +err_clk_enable: + clk_put(clk); + return ret; +} + +static int __init lpc32xx_clockevent_init(struct device_node *np) +{ + void __iomem *base; + unsigned long rate; + struct clk *clk; + int ret, irq; + + clk = of_clk_get_by_name(np, "timerclk"); + if (IS_ERR(clk)) { + pr_err("clock get failed (%lu)\n", PTR_ERR(clk)); + return PTR_ERR(clk); + } + + ret = clk_prepare_enable(clk); + if (ret) { + pr_err("clock enable failed (%d)\n", ret); + goto err_clk_enable; + } + + base = of_iomap(np, 0); + if (!base) { + pr_err("unable to map registers\n"); + ret = -EADDRNOTAVAIL; + goto err_iomap; + } + + irq = irq_of_parse_and_map(np, 0); + if (!irq) { + pr_err("get irq failed\n"); + ret = -ENOENT; + goto err_irq; + } + + /* + * Disable timer and clear any pending interrupt (IR) on match + * channel 0 (MR0). Configure a compare match value of 1 on MR0 + * and enable interrupt, reset on match and stop on match (MCR). + */ + writel_relaxed(0, base + LPC32XX_TIMER_TCR); + writel_relaxed(0, base + LPC32XX_TIMER_CTCR); + writel_relaxed(LPC32XX_TIMER_IR_MR0INT, base + LPC32XX_TIMER_IR); + writel_relaxed(1, base + LPC32XX_TIMER_MR0); + writel_relaxed(LPC32XX_TIMER_MCR_MR0I | LPC32XX_TIMER_MCR_MR0R | + LPC32XX_TIMER_MCR_MR0S, base + LPC32XX_TIMER_MCR); + + rate = clk_get_rate(clk); + lpc32xx_clk_event_ddata.base = base; + clockevents_config_and_register(&lpc32xx_clk_event_ddata.evtdev, + rate, 1, -1); + + ret = request_irq(irq, lpc32xx_clock_event_handler, + IRQF_TIMER | IRQF_IRQPOLL, "lpc3220 clockevent", + &lpc32xx_clk_event_ddata); + if (ret) { + pr_err("request irq failed\n"); + goto err_irq; + } + + return 0; + +err_irq: + iounmap(base); +err_iomap: + clk_disable_unprepare(clk); +err_clk_enable: + clk_put(clk); + return ret; +} + +/* + * This function asserts that we have exactly one clocksource and one + * clock_event_device in the end. + */ +static void __init lpc32xx_timer_init(struct device_node *np) +{ + static int has_clocksource, has_clockevent; + int ret; + + if (!has_clocksource) { + ret = lpc32xx_clocksource_init(np); + if (!ret) { + has_clocksource = 1; + return; + } + } + + if (!has_clockevent) { + ret = lpc32xx_clockevent_init(np); + if (!ret) { + has_clockevent = 1; + return; + } + } +} +CLOCKSOURCE_OF_DECLARE(lpc32xx_timer, "nxp,lpc3220-timer", lpc32xx_timer_init); diff --git a/drivers/clocksource/timer-imx-gpt.c b/drivers/clocksource/timer-imx-gpt.c new file mode 100644 index 000000000000..879c78423546 --- /dev/null +++ b/drivers/clocksource/timer-imx-gpt.c @@ -0,0 +1,540 @@ +/* + * linux/arch/arm/plat-mxc/time.c + * + * Copyright (C) 2000-2001 Deep Blue Solutions + * Copyright (C) 2002 Shane Nay (shane@minirl.com) + * Copyright (C) 2006-2007 Pavel Pisa (ppisa@pikron.com) + * Copyright (C) 2008 Juergen Beisert (kernel@pengutronix.de) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/clockchips.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/sched_clock.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <soc/imx/timer.h> + +/* + * There are 4 versions of the timer hardware on Freescale MXC hardware. + * - MX1/MXL + * - MX21, MX27. + * - MX25, MX31, MX35, MX37, MX51, MX6Q(rev1.0) + * - MX6DL, MX6SX, MX6Q(rev1.1+) + */ + +/* defines common for all i.MX */ +#define MXC_TCTL 0x00 +#define MXC_TCTL_TEN (1 << 0) /* Enable module */ +#define MXC_TPRER 0x04 + +/* MX1, MX21, MX27 */ +#define MX1_2_TCTL_CLK_PCLK1 (1 << 1) +#define MX1_2_TCTL_IRQEN (1 << 4) +#define MX1_2_TCTL_FRR (1 << 8) +#define MX1_2_TCMP 0x08 +#define MX1_2_TCN 0x10 +#define MX1_2_TSTAT 0x14 + +/* MX21, MX27 */ +#define MX2_TSTAT_CAPT (1 << 1) +#define MX2_TSTAT_COMP (1 << 0) + +/* MX31, MX35, MX25, MX5, MX6 */ +#define V2_TCTL_WAITEN (1 << 3) /* Wait enable mode */ +#define V2_TCTL_CLK_IPG (1 << 6) +#define V2_TCTL_CLK_PER (2 << 6) +#define V2_TCTL_CLK_OSC_DIV8 (5 << 6) +#define V2_TCTL_FRR (1 << 9) +#define V2_TCTL_24MEN (1 << 10) +#define V2_TPRER_PRE24M 12 +#define V2_IR 0x0c +#define V2_TSTAT 0x08 +#define V2_TSTAT_OF1 (1 << 0) +#define V2_TCN 0x24 +#define V2_TCMP 0x10 + +#define V2_TIMER_RATE_OSC_DIV8 3000000 + +struct imx_timer { + enum imx_gpt_type type; + void __iomem *base; + int irq; + struct clk *clk_per; + struct clk *clk_ipg; + const struct imx_gpt_data *gpt; + struct clock_event_device ced; + enum clock_event_mode cem; + struct irqaction act; +}; + +struct imx_gpt_data { + int reg_tstat; + int reg_tcn; + int reg_tcmp; + void (*gpt_setup_tctl)(struct imx_timer *imxtm); + void (*gpt_irq_enable)(struct imx_timer *imxtm); + void (*gpt_irq_disable)(struct imx_timer *imxtm); + void (*gpt_irq_acknowledge)(struct imx_timer *imxtm); + int (*set_next_event)(unsigned long evt, + struct clock_event_device *ced); +}; + +static inline struct imx_timer *to_imx_timer(struct clock_event_device *ced) +{ + return container_of(ced, struct imx_timer, ced); +} + +static void imx1_gpt_irq_disable(struct imx_timer *imxtm) +{ + unsigned int tmp; + + tmp = readl_relaxed(imxtm->base + MXC_TCTL); + writel_relaxed(tmp & ~MX1_2_TCTL_IRQEN, imxtm->base + MXC_TCTL); +} +#define imx21_gpt_irq_disable imx1_gpt_irq_disable + +static void imx31_gpt_irq_disable(struct imx_timer *imxtm) +{ + writel_relaxed(0, imxtm->base + V2_IR); +} +#define imx6dl_gpt_irq_disable imx31_gpt_irq_disable + +static void imx1_gpt_irq_enable(struct imx_timer *imxtm) +{ + unsigned int tmp; + + tmp = readl_relaxed(imxtm->base + MXC_TCTL); + writel_relaxed(tmp | MX1_2_TCTL_IRQEN, imxtm->base + MXC_TCTL); +} +#define imx21_gpt_irq_enable imx1_gpt_irq_enable + +static void imx31_gpt_irq_enable(struct imx_timer *imxtm) +{ + writel_relaxed(1<<0, imxtm->base + V2_IR); +} +#define imx6dl_gpt_irq_enable imx31_gpt_irq_enable + +static void imx1_gpt_irq_acknowledge(struct imx_timer *imxtm) +{ + writel_relaxed(0, imxtm->base + MX1_2_TSTAT); +} + +static void imx21_gpt_irq_acknowledge(struct imx_timer *imxtm) +{ + writel_relaxed(MX2_TSTAT_CAPT | MX2_TSTAT_COMP, + imxtm->base + MX1_2_TSTAT); +} + +static void imx31_gpt_irq_acknowledge(struct imx_timer *imxtm) +{ + writel_relaxed(V2_TSTAT_OF1, imxtm->base + V2_TSTAT); +} +#define imx6dl_gpt_irq_acknowledge imx31_gpt_irq_acknowledge + +static void __iomem *sched_clock_reg; + +static u64 notrace mxc_read_sched_clock(void) +{ + return sched_clock_reg ? readl_relaxed(sched_clock_reg) : 0; +} + +static struct delay_timer imx_delay_timer; + +static unsigned long imx_read_current_timer(void) +{ + return readl_relaxed(sched_clock_reg); +} + +static int __init mxc_clocksource_init(struct imx_timer *imxtm) +{ + unsigned int c = clk_get_rate(imxtm->clk_per); + void __iomem *reg = imxtm->base + imxtm->gpt->reg_tcn; + + imx_delay_timer.read_current_timer = &imx_read_current_timer; + imx_delay_timer.freq = c; + register_current_timer_delay(&imx_delay_timer); + + sched_clock_reg = reg; + + sched_clock_register(mxc_read_sched_clock, 32, c); + return clocksource_mmio_init(reg, "mxc_timer1", c, 200, 32, + clocksource_mmio_readl_up); +} + +/* clock event */ + +static int mx1_2_set_next_event(unsigned long evt, + struct clock_event_device *ced) +{ + struct imx_timer *imxtm = to_imx_timer(ced); + unsigned long tcmp; + + tcmp = readl_relaxed(imxtm->base + MX1_2_TCN) + evt; + + writel_relaxed(tcmp, imxtm->base + MX1_2_TCMP); + + return (int)(tcmp - readl_relaxed(imxtm->base + MX1_2_TCN)) < 0 ? + -ETIME : 0; +} + +static int v2_set_next_event(unsigned long evt, + struct clock_event_device *ced) +{ + struct imx_timer *imxtm = to_imx_timer(ced); + unsigned long tcmp; + + tcmp = readl_relaxed(imxtm->base + V2_TCN) + evt; + + writel_relaxed(tcmp, imxtm->base + V2_TCMP); + + return evt < 0x7fffffff && + (int)(tcmp - readl_relaxed(imxtm->base + V2_TCN)) < 0 ? + -ETIME : 0; +} + +#ifdef DEBUG +static const char *clock_event_mode_label[] = { + [CLOCK_EVT_MODE_PERIODIC] = "CLOCK_EVT_MODE_PERIODIC", + [CLOCK_EVT_MODE_ONESHOT] = "CLOCK_EVT_MODE_ONESHOT", + [CLOCK_EVT_MODE_SHUTDOWN] = "CLOCK_EVT_MODE_SHUTDOWN", + [CLOCK_EVT_MODE_UNUSED] = "CLOCK_EVT_MODE_UNUSED", + [CLOCK_EVT_MODE_RESUME] = "CLOCK_EVT_MODE_RESUME", +}; +#endif /* DEBUG */ + +static void mxc_set_mode(enum clock_event_mode mode, + struct clock_event_device *ced) +{ + struct imx_timer *imxtm = to_imx_timer(ced); + unsigned long flags; + + /* + * The timer interrupt generation is disabled at least + * for enough time to call mxc_set_next_event() + */ + local_irq_save(flags); + + /* Disable interrupt in GPT module */ + imxtm->gpt->gpt_irq_disable(imxtm); + + if (mode != imxtm->cem) { + u32 tcn = readl_relaxed(imxtm->base + imxtm->gpt->reg_tcn); + /* Set event time into far-far future */ + writel_relaxed(tcn - 3, imxtm->base + imxtm->gpt->reg_tcmp); + + /* Clear pending interrupt */ + imxtm->gpt->gpt_irq_acknowledge(imxtm); + } + +#ifdef DEBUG + printk(KERN_INFO "mxc_set_mode: changing mode from %s to %s\n", + clock_event_mode_label[imxtm->cem], + clock_event_mode_label[mode]); +#endif /* DEBUG */ + + /* Remember timer mode */ + imxtm->cem = mode; + local_irq_restore(flags); + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + printk(KERN_ERR"mxc_set_mode: Periodic mode is not " + "supported for i.MX\n"); + break; + case CLOCK_EVT_MODE_ONESHOT: + /* + * Do not put overhead of interrupt enable/disable into + * mxc_set_next_event(), the core has about 4 minutes + * to call mxc_set_next_event() or shutdown clock after + * mode switching + */ + local_irq_save(flags); + imxtm->gpt->gpt_irq_enable(imxtm); + local_irq_restore(flags); + break; + case CLOCK_EVT_MODE_SHUTDOWN: + case CLOCK_EVT_MODE_UNUSED: + case CLOCK_EVT_MODE_RESUME: + /* Left event sources disabled, no more interrupts appear */ + break; + } +} + +/* + * IRQ handler for the timer + */ +static irqreturn_t mxc_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *ced = dev_id; + struct imx_timer *imxtm = to_imx_timer(ced); + uint32_t tstat; + + tstat = readl_relaxed(imxtm->base + imxtm->gpt->reg_tstat); + + imxtm->gpt->gpt_irq_acknowledge(imxtm); + + ced->event_handler(ced); + + return IRQ_HANDLED; +} + +static int __init mxc_clockevent_init(struct imx_timer *imxtm) +{ + struct clock_event_device *ced = &imxtm->ced; + struct irqaction *act = &imxtm->act; + + imxtm->cem = CLOCK_EVT_MODE_UNUSED; + + ced->name = "mxc_timer1"; + ced->features = CLOCK_EVT_FEAT_ONESHOT; + ced->set_mode = mxc_set_mode; + ced->set_next_event = imxtm->gpt->set_next_event; + ced->rating = 200; + ced->cpumask = cpumask_of(0); + clockevents_config_and_register(ced, clk_get_rate(imxtm->clk_per), + 0xff, 0xfffffffe); + + act->name = "i.MX Timer Tick"; + act->flags = IRQF_TIMER | IRQF_IRQPOLL; + act->handler = mxc_timer_interrupt; + act->dev_id = ced; + + return setup_irq(imxtm->irq, act); +} + +static void imx1_gpt_setup_tctl(struct imx_timer *imxtm) +{ + u32 tctl_val; + + tctl_val = MX1_2_TCTL_FRR | MX1_2_TCTL_CLK_PCLK1 | MXC_TCTL_TEN; + writel_relaxed(tctl_val, imxtm->base + MXC_TCTL); +} +#define imx21_gpt_setup_tctl imx1_gpt_setup_tctl + +static void imx31_gpt_setup_tctl(struct imx_timer *imxtm) +{ + u32 tctl_val; + + tctl_val = V2_TCTL_FRR | V2_TCTL_WAITEN | MXC_TCTL_TEN; + if (clk_get_rate(imxtm->clk_per) == V2_TIMER_RATE_OSC_DIV8) + tctl_val |= V2_TCTL_CLK_OSC_DIV8; + else + tctl_val |= V2_TCTL_CLK_PER; + + writel_relaxed(tctl_val, imxtm->base + MXC_TCTL); +} + +static void imx6dl_gpt_setup_tctl(struct imx_timer *imxtm) +{ + u32 tctl_val; + + tctl_val = V2_TCTL_FRR | V2_TCTL_WAITEN | MXC_TCTL_TEN; + if (clk_get_rate(imxtm->clk_per) == V2_TIMER_RATE_OSC_DIV8) { + tctl_val |= V2_TCTL_CLK_OSC_DIV8; + /* 24 / 8 = 3 MHz */ + writel_relaxed(7 << V2_TPRER_PRE24M, imxtm->base + MXC_TPRER); + tctl_val |= V2_TCTL_24MEN; + } else { + tctl_val |= V2_TCTL_CLK_PER; + } + + writel_relaxed(tctl_val, imxtm->base + MXC_TCTL); +} + +static const struct imx_gpt_data imx1_gpt_data = { + .reg_tstat = MX1_2_TSTAT, + .reg_tcn = MX1_2_TCN, + .reg_tcmp = MX1_2_TCMP, + .gpt_irq_enable = imx1_gpt_irq_enable, + .gpt_irq_disable = imx1_gpt_irq_disable, + .gpt_irq_acknowledge = imx1_gpt_irq_acknowledge, + .gpt_setup_tctl = imx1_gpt_setup_tctl, + .set_next_event = mx1_2_set_next_event, +}; + +static const struct imx_gpt_data imx21_gpt_data = { + .reg_tstat = MX1_2_TSTAT, + .reg_tcn = MX1_2_TCN, + .reg_tcmp = MX1_2_TCMP, + .gpt_irq_enable = imx21_gpt_irq_enable, + .gpt_irq_disable = imx21_gpt_irq_disable, + .gpt_irq_acknowledge = imx21_gpt_irq_acknowledge, + .gpt_setup_tctl = imx21_gpt_setup_tctl, + .set_next_event = mx1_2_set_next_event, +}; + +static const struct imx_gpt_data imx31_gpt_data = { + .reg_tstat = V2_TSTAT, + .reg_tcn = V2_TCN, + .reg_tcmp = V2_TCMP, + .gpt_irq_enable = imx31_gpt_irq_enable, + .gpt_irq_disable = imx31_gpt_irq_disable, + .gpt_irq_acknowledge = imx31_gpt_irq_acknowledge, + .gpt_setup_tctl = imx31_gpt_setup_tctl, + .set_next_event = v2_set_next_event, +}; + +static const struct imx_gpt_data imx6dl_gpt_data = { + .reg_tstat = V2_TSTAT, + .reg_tcn = V2_TCN, + .reg_tcmp = V2_TCMP, + .gpt_irq_enable = imx6dl_gpt_irq_enable, + .gpt_irq_disable = imx6dl_gpt_irq_disable, + .gpt_irq_acknowledge = imx6dl_gpt_irq_acknowledge, + .gpt_setup_tctl = imx6dl_gpt_setup_tctl, + .set_next_event = v2_set_next_event, +}; + +static void __init _mxc_timer_init(struct imx_timer *imxtm) +{ + switch (imxtm->type) { + case GPT_TYPE_IMX1: + imxtm->gpt = &imx1_gpt_data; + break; + case GPT_TYPE_IMX21: + imxtm->gpt = &imx21_gpt_data; + break; + case GPT_TYPE_IMX31: + imxtm->gpt = &imx31_gpt_data; + break; + case GPT_TYPE_IMX6DL: + imxtm->gpt = &imx6dl_gpt_data; + break; + default: + BUG(); + } + + if (IS_ERR(imxtm->clk_per)) { + pr_err("i.MX timer: unable to get clk\n"); + return; + } + + if (!IS_ERR(imxtm->clk_ipg)) + clk_prepare_enable(imxtm->clk_ipg); + + clk_prepare_enable(imxtm->clk_per); + + /* + * Initialise to a known state (all timers off, and timing reset) + */ + + writel_relaxed(0, imxtm->base + MXC_TCTL); + writel_relaxed(0, imxtm->base + MXC_TPRER); /* see datasheet note */ + + imxtm->gpt->gpt_setup_tctl(imxtm); + + /* init and register the timer to the framework */ + mxc_clocksource_init(imxtm); + mxc_clockevent_init(imxtm); +} + +void __init mxc_timer_init(unsigned long pbase, int irq, enum imx_gpt_type type) +{ + struct imx_timer *imxtm; + + imxtm = kzalloc(sizeof(*imxtm), GFP_KERNEL); + BUG_ON(!imxtm); + + imxtm->clk_per = clk_get_sys("imx-gpt.0", "per"); + imxtm->clk_ipg = clk_get_sys("imx-gpt.0", "ipg"); + + imxtm->base = ioremap(pbase, SZ_4K); + BUG_ON(!imxtm->base); + + imxtm->type = type; + + _mxc_timer_init(imxtm); +} + +static void __init mxc_timer_init_dt(struct device_node *np, enum imx_gpt_type type) +{ + struct imx_timer *imxtm; + static int initialized; + + /* Support one instance only */ + if (initialized) + return; + + imxtm = kzalloc(sizeof(*imxtm), GFP_KERNEL); + BUG_ON(!imxtm); + + imxtm->base = of_iomap(np, 0); + WARN_ON(!imxtm->base); + imxtm->irq = irq_of_parse_and_map(np, 0); + + imxtm->clk_ipg = of_clk_get_by_name(np, "ipg"); + + /* Try osc_per first, and fall back to per otherwise */ + imxtm->clk_per = of_clk_get_by_name(np, "osc_per"); + if (IS_ERR(imxtm->clk_per)) + imxtm->clk_per = of_clk_get_by_name(np, "per"); + + imxtm->type = type; + + _mxc_timer_init(imxtm); + + initialized = 1; +} + +static void __init imx1_timer_init_dt(struct device_node *np) +{ + mxc_timer_init_dt(np, GPT_TYPE_IMX1); +} + +static void __init imx21_timer_init_dt(struct device_node *np) +{ + mxc_timer_init_dt(np, GPT_TYPE_IMX21); +} + +static void __init imx31_timer_init_dt(struct device_node *np) +{ + enum imx_gpt_type type = GPT_TYPE_IMX31; + + /* + * We were using the same compatible string for i.MX6Q/D and i.MX6DL/S + * GPT device, while they actually have different programming model. + * This is a workaround to keep the existing i.MX6DL/S DTBs continue + * working with the new kernel. + */ + if (of_machine_is_compatible("fsl,imx6dl")) + type = GPT_TYPE_IMX6DL; + + mxc_timer_init_dt(np, type); +} + +static void __init imx6dl_timer_init_dt(struct device_node *np) +{ + mxc_timer_init_dt(np, GPT_TYPE_IMX6DL); +} + +CLOCKSOURCE_OF_DECLARE(imx1_timer, "fsl,imx1-gpt", imx1_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx21_timer, "fsl,imx21-gpt", imx21_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx31_timer, "fsl,imx31-gpt", imx31_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx25_timer, "fsl,imx25-gpt", imx31_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx50_timer, "fsl,imx50-gpt", imx31_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx51_timer, "fsl,imx51-gpt", imx31_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx53_timer, "fsl,imx53-gpt", imx31_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx6q_timer, "fsl,imx6q-gpt", imx31_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx6dl_timer, "fsl,imx6dl-gpt", imx6dl_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx6sl_timer, "fsl,imx6sl-gpt", imx6dl_timer_init_dt); +CLOCKSOURCE_OF_DECLARE(imx6sx_timer, "fsl,imx6sx-gpt", imx6dl_timer_init_dt); diff --git a/drivers/clocksource/timer-integrator-ap.c b/drivers/clocksource/timer-integrator-ap.c index b9efd30513d5..c97d1980c0f8 100644 --- a/drivers/clocksource/timer-integrator-ap.c +++ b/drivers/clocksource/timer-integrator-ap.c @@ -166,7 +166,7 @@ static void __init integrator_ap_timer_init_of(struct device_node *node) struct device_node *sec_node; base = of_io_request_and_map(node, 0, "integrator-timer"); - if (!base) + if (IS_ERR(base)) return; clk = of_clk_get(node, 0); diff --git a/drivers/clocksource/timer-stm32.c b/drivers/clocksource/timer-stm32.c new file mode 100644 index 000000000000..a97e8b50701c --- /dev/null +++ b/drivers/clocksource/timer-stm32.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) Maxime Coquelin 2015 + * Author: Maxime Coquelin <mcoquelin.stm32@gmail.com> + * License terms: GNU General Public License (GPL), version 2 + * + * Inspired by time-efm32.c from Uwe Kleine-Koenig + */ + +#include <linux/kernel.h> +#include <linux/clocksource.h> +#include <linux/clockchips.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/clk.h> +#include <linux/reset.h> + +#define TIM_CR1 0x00 +#define TIM_DIER 0x0c +#define TIM_SR 0x10 +#define TIM_EGR 0x14 +#define TIM_PSC 0x28 +#define TIM_ARR 0x2c + +#define TIM_CR1_CEN BIT(0) +#define TIM_CR1_OPM BIT(3) +#define TIM_CR1_ARPE BIT(7) + +#define TIM_DIER_UIE BIT(0) + +#define TIM_SR_UIF BIT(0) + +#define TIM_EGR_UG BIT(0) + +struct stm32_clock_event_ddata { + struct clock_event_device evtdev; + unsigned periodic_top; + void __iomem *base; +}; + +static void stm32_clock_event_set_mode(enum clock_event_mode mode, + struct clock_event_device *evtdev) +{ + struct stm32_clock_event_ddata *data = + container_of(evtdev, struct stm32_clock_event_ddata, evtdev); + void *base = data->base; + + switch (mode) { + case CLOCK_EVT_MODE_PERIODIC: + writel_relaxed(data->periodic_top, base + TIM_ARR); + writel_relaxed(TIM_CR1_ARPE | TIM_CR1_CEN, base + TIM_CR1); + break; + + case CLOCK_EVT_MODE_ONESHOT: + default: + writel_relaxed(0, base + TIM_CR1); + break; + } +} + +static int stm32_clock_event_set_next_event(unsigned long evt, + struct clock_event_device *evtdev) +{ + struct stm32_clock_event_ddata *data = + container_of(evtdev, struct stm32_clock_event_ddata, evtdev); + + writel_relaxed(evt, data->base + TIM_ARR); + writel_relaxed(TIM_CR1_ARPE | TIM_CR1_OPM | TIM_CR1_CEN, + data->base + TIM_CR1); + + return 0; +} + +static irqreturn_t stm32_clock_event_handler(int irq, void *dev_id) +{ + struct stm32_clock_event_ddata *data = dev_id; + + writel_relaxed(0, data->base + TIM_SR); + + data->evtdev.event_handler(&data->evtdev); + + return IRQ_HANDLED; +} + +static struct stm32_clock_event_ddata clock_event_ddata = { + .evtdev = { + .name = "stm32 clockevent", + .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC, + .set_mode = stm32_clock_event_set_mode, + .set_next_event = stm32_clock_event_set_next_event, + .rating = 200, + }, +}; + +static void __init stm32_clockevent_init(struct device_node *np) +{ + struct stm32_clock_event_ddata *data = &clock_event_ddata; + struct clk *clk; + struct reset_control *rstc; + unsigned long rate, max_delta; + int irq, ret, bits, prescaler = 1; + + clk = of_clk_get(np, 0); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + pr_err("failed to get clock for clockevent (%d)\n", ret); + goto err_clk_get; + } + + ret = clk_prepare_enable(clk); + if (ret) { + pr_err("failed to enable timer clock for clockevent (%d)\n", + ret); + goto err_clk_enable; + } + + rate = clk_get_rate(clk); + + rstc = of_reset_control_get(np, NULL); + if (!IS_ERR(rstc)) { + reset_control_assert(rstc); + reset_control_deassert(rstc); + } + + data->base = of_iomap(np, 0); + if (!data->base) { + pr_err("failed to map registers for clockevent\n"); + goto err_iomap; + } + + irq = irq_of_parse_and_map(np, 0); + if (!irq) { + pr_err("%s: failed to get irq.\n", np->full_name); + goto err_get_irq; + } + + /* Detect whether the timer is 16 or 32 bits */ + writel_relaxed(~0U, data->base + TIM_ARR); + max_delta = readl_relaxed(data->base + TIM_ARR); + if (max_delta == ~0U) { + prescaler = 1; + bits = 32; + } else { + prescaler = 1024; + bits = 16; + } + writel_relaxed(0, data->base + TIM_ARR); + + writel_relaxed(prescaler - 1, data->base + TIM_PSC); + writel_relaxed(TIM_EGR_UG, data->base + TIM_EGR); + writel_relaxed(TIM_DIER_UIE, data->base + TIM_DIER); + writel_relaxed(0, data->base + TIM_SR); + + data->periodic_top = DIV_ROUND_CLOSEST(rate, prescaler * HZ); + + clockevents_config_and_register(&data->evtdev, + DIV_ROUND_CLOSEST(rate, prescaler), + 0x1, max_delta); + + ret = request_irq(irq, stm32_clock_event_handler, IRQF_TIMER, + "stm32 clockevent", data); + if (ret) { + pr_err("%s: failed to request irq.\n", np->full_name); + goto err_get_irq; + } + + pr_info("%s: STM32 clockevent driver initialized (%d bits)\n", + np->full_name, bits); + + return; + +err_get_irq: + iounmap(data->base); +err_iomap: + clk_disable_unprepare(clk); +err_clk_enable: + clk_put(clk); +err_clk_get: + return; +} + +CLOCKSOURCE_OF_DECLARE(stm32, "st,stm32-timer", stm32_clockevent_init); diff --git a/drivers/clocksource/timer-sun5i.c b/drivers/clocksource/timer-sun5i.c index 28aa4b7bb602..0ffb4ea7c925 100644 --- a/drivers/clocksource/timer-sun5i.c +++ b/drivers/clocksource/timer-sun5i.c @@ -324,7 +324,7 @@ static void __init sun5i_timer_init(struct device_node *node) int irq; timer_base = of_io_request_and_map(node, 0, of_node_full_name(node)); - if (!timer_base) + if (IS_ERR(timer_base)) panic("Can't map registers"); irq = irq_of_parse_and_map(node, 0); |