diff options
Diffstat (limited to 'drivers/clocksource')
-rw-r--r-- | drivers/clocksource/Kconfig | 8 | ||||
-rw-r--r-- | drivers/clocksource/Makefile | 1 | ||||
-rw-r--r-- | drivers/clocksource/arm_arch_timer.c | 6 | ||||
-rw-r--r-- | drivers/clocksource/bcm2835_timer.c | 1 | ||||
-rw-r--r-- | drivers/clocksource/tango_xtal.c | 6 | ||||
-rw-r--r-- | drivers/clocksource/timer-imx-tpm.c | 239 | ||||
-rw-r--r-- | drivers/clocksource/timer-of.c | 11 | ||||
-rw-r--r-- | drivers/clocksource/timer-probe.c | 3 | ||||
-rw-r--r-- | drivers/clocksource/timer-stm32.c | 8 |
9 files changed, 266 insertions, 17 deletions
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig index 54a67f8a28eb..cc6062049170 100644 --- a/drivers/clocksource/Kconfig +++ b/drivers/clocksource/Kconfig @@ -598,6 +598,14 @@ config CLKSRC_IMX_GPT depends on ARM && CLKDEV_LOOKUP select CLKSRC_MMIO +config CLKSRC_IMX_TPM + bool "Clocksource using i.MX TPM" if COMPILE_TEST + depends on ARM && CLKDEV_LOOKUP && GENERIC_CLOCKEVENTS + select CLKSRC_MMIO + help + Enable this option to use IMX Timer/PWM Module (TPM) timer as + clocksource. + config CLKSRC_ST_LPC bool "Low power clocksource found in the LPC" if COMPILE_TEST select TIMER_OF if OF diff --git a/drivers/clocksource/Makefile b/drivers/clocksource/Makefile index 6df949402dfc..dbc1ad14515e 100644 --- a/drivers/clocksource/Makefile +++ b/drivers/clocksource/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_CLKSRC_VERSATILE) += versatile.o obj-$(CONFIG_CLKSRC_MIPS_GIC) += mips-gic-timer.o obj-$(CONFIG_CLKSRC_TANGO_XTAL) += tango_xtal.o obj-$(CONFIG_CLKSRC_IMX_GPT) += timer-imx-gpt.o +obj-$(CONFIG_CLKSRC_IMX_TPM) += timer-imx-tpm.o obj-$(CONFIG_ASM9260_TIMER) += asm9260_timer.o obj-$(CONFIG_H8300_TMR8) += h8300_timer8.o obj-$(CONFIG_H8300_TMR16) += h8300_timer16.o diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c index 72bbfccef113..fd4b7f684bd0 100644 --- a/drivers/clocksource/arm_arch_timer.c +++ b/drivers/clocksource/arm_arch_timer.c @@ -455,7 +455,11 @@ void arch_timer_enable_workaround(const struct arch_timer_erratum_workaround *wa per_cpu(timer_unstable_counter_workaround, i) = wa; } - static_branch_enable(&arch_timer_read_ool_enabled); + /* + * Use the locked version, as we're called from the CPU + * hotplug framework. Otherwise, we end-up in deadlock-land. + */ + static_branch_enable_cpuslocked(&arch_timer_read_ool_enabled); /* * Don't use the vdso fastpath if errata require using the diff --git a/drivers/clocksource/bcm2835_timer.c b/drivers/clocksource/bcm2835_timer.c index 82828d3a4739..39e489a96ad7 100644 --- a/drivers/clocksource/bcm2835_timer.c +++ b/drivers/clocksource/bcm2835_timer.c @@ -114,7 +114,6 @@ static int __init bcm2835_timer_init(struct device_node *node) timer = kzalloc(sizeof(*timer), GFP_KERNEL); if (!timer) { - pr_err("Can't allocate timer struct\n"); ret = -ENOMEM; goto err_iounmap; } diff --git a/drivers/clocksource/tango_xtal.c b/drivers/clocksource/tango_xtal.c index c4e1c2e6046f..6a8d9838ce33 100644 --- a/drivers/clocksource/tango_xtal.c +++ b/drivers/clocksource/tango_xtal.c @@ -26,13 +26,13 @@ static int __init tango_clocksource_init(struct device_node *np) xtal_in_cnt = of_iomap(np, 0); if (xtal_in_cnt == NULL) { - pr_err("%s: invalid address\n", np->full_name); + pr_err("%pOF: invalid address\n", np); return -ENXIO; } clk = of_clk_get(np, 0); if (IS_ERR(clk)) { - pr_err("%s: invalid clock\n", np->full_name); + pr_err("%pOF: invalid clock\n", np); return PTR_ERR(clk); } @@ -43,7 +43,7 @@ static int __init tango_clocksource_init(struct device_node *np) ret = clocksource_mmio_init(xtal_in_cnt, "tango-xtal", xtal_freq, 350, 32, clocksource_mmio_readl_up); if (ret) { - pr_err("%s: registration failed\n", np->full_name); + pr_err("%pOF: registration failed\n", np); return ret; } diff --git a/drivers/clocksource/timer-imx-tpm.c b/drivers/clocksource/timer-imx-tpm.c new file mode 100644 index 000000000000..21bffdcb2f20 --- /dev/null +++ b/drivers/clocksource/timer-imx-tpm.c @@ -0,0 +1,239 @@ +/* + * Copyright 2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + * + * 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. + */ + +#include <linux/clk.h> +#include <linux/clockchips.h> +#include <linux/clocksource.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/sched_clock.h> + +#define TPM_SC 0x10 +#define TPM_SC_CMOD_INC_PER_CNT (0x1 << 3) +#define TPM_SC_CMOD_DIV_DEFAULT 0x3 +#define TPM_CNT 0x14 +#define TPM_MOD 0x18 +#define TPM_STATUS 0x1c +#define TPM_STATUS_CH0F BIT(0) +#define TPM_C0SC 0x20 +#define TPM_C0SC_CHIE BIT(6) +#define TPM_C0SC_MODE_SHIFT 2 +#define TPM_C0SC_MODE_MASK 0x3c +#define TPM_C0SC_MODE_SW_COMPARE 0x4 +#define TPM_C0V 0x24 + +static void __iomem *timer_base; +static struct clock_event_device clockevent_tpm; + +static inline void tpm_timer_disable(void) +{ + unsigned int val; + + /* channel disable */ + val = readl(timer_base + TPM_C0SC); + val &= ~(TPM_C0SC_MODE_MASK | TPM_C0SC_CHIE); + writel(val, timer_base + TPM_C0SC); +} + +static inline void tpm_timer_enable(void) +{ + unsigned int val; + + /* channel enabled in sw compare mode */ + val = readl(timer_base + TPM_C0SC); + val |= (TPM_C0SC_MODE_SW_COMPARE << TPM_C0SC_MODE_SHIFT) | + TPM_C0SC_CHIE; + writel(val, timer_base + TPM_C0SC); +} + +static inline void tpm_irq_acknowledge(void) +{ + writel(TPM_STATUS_CH0F, timer_base + TPM_STATUS); +} + +static struct delay_timer tpm_delay_timer; + +static inline unsigned long tpm_read_counter(void) +{ + return readl(timer_base + TPM_CNT); +} + +static unsigned long tpm_read_current_timer(void) +{ + return tpm_read_counter(); +} + +static u64 notrace tpm_read_sched_clock(void) +{ + return tpm_read_counter(); +} + +static int __init tpm_clocksource_init(unsigned long rate) +{ + tpm_delay_timer.read_current_timer = &tpm_read_current_timer; + tpm_delay_timer.freq = rate; + register_current_timer_delay(&tpm_delay_timer); + + sched_clock_register(tpm_read_sched_clock, 32, rate); + + return clocksource_mmio_init(timer_base + TPM_CNT, "imx-tpm", + rate, 200, 32, clocksource_mmio_readl_up); +} + +static int tpm_set_next_event(unsigned long delta, + struct clock_event_device *evt) +{ + unsigned long next, now; + + next = tpm_read_counter(); + next += delta; + writel(next, timer_base + TPM_C0V); + now = tpm_read_counter(); + + /* + * NOTE: We observed in a very small probability, the bus fabric + * contention between GPU and A7 may results a few cycles delay + * of writing CNT registers which may cause the min_delta event got + * missed, so we need add a ETIME check here in case it happened. + */ + return (int)((next - now) <= 0) ? -ETIME : 0; +} + +static int tpm_set_state_oneshot(struct clock_event_device *evt) +{ + tpm_timer_enable(); + + return 0; +} + +static int tpm_set_state_shutdown(struct clock_event_device *evt) +{ + tpm_timer_disable(); + + return 0; +} + +static irqreturn_t tpm_timer_interrupt(int irq, void *dev_id) +{ + struct clock_event_device *evt = dev_id; + + tpm_irq_acknowledge(); + + evt->event_handler(evt); + + return IRQ_HANDLED; +} + +static struct clock_event_device clockevent_tpm = { + .name = "i.MX7ULP TPM Timer", + .features = CLOCK_EVT_FEAT_ONESHOT, + .set_state_oneshot = tpm_set_state_oneshot, + .set_next_event = tpm_set_next_event, + .set_state_shutdown = tpm_set_state_shutdown, + .rating = 200, +}; + +static int __init tpm_clockevent_init(unsigned long rate, int irq) +{ + int ret; + + ret = request_irq(irq, tpm_timer_interrupt, IRQF_TIMER | IRQF_IRQPOLL, + "i.MX7ULP TPM Timer", &clockevent_tpm); + + clockevent_tpm.cpumask = cpumask_of(0); + clockevent_tpm.irq = irq; + clockevents_config_and_register(&clockevent_tpm, + rate, 300, 0xfffffffe); + + return ret; +} + +static int __init tpm_timer_init(struct device_node *np) +{ + struct clk *ipg, *per; + int irq, ret; + u32 rate; + + timer_base = of_iomap(np, 0); + if (!timer_base) { + pr_err("tpm: failed to get base address\n"); + return -ENXIO; + } + + irq = irq_of_parse_and_map(np, 0); + if (!irq) { + pr_err("tpm: failed to get irq\n"); + ret = -ENOENT; + goto err_iomap; + } + + ipg = of_clk_get_by_name(np, "ipg"); + per = of_clk_get_by_name(np, "per"); + if (IS_ERR(ipg) || IS_ERR(per)) { + pr_err("tpm: failed to get igp or per clk\n"); + ret = -ENODEV; + goto err_clk_get; + } + + /* enable clk before accessing registers */ + ret = clk_prepare_enable(ipg); + if (ret) { + pr_err("tpm: ipg clock enable failed (%d)\n", ret); + goto err_clk_get; + } + + ret = clk_prepare_enable(per); + if (ret) { + pr_err("tpm: per clock enable failed (%d)\n", ret); + goto err_per_clk_enable; + } + + /* + * Initialize tpm module to a known state + * 1) Counter disabled + * 2) TPM counter operates in up counting mode + * 3) Timer Overflow Interrupt disabled + * 4) Channel0 disabled + * 5) DMA transfers disabled + */ + writel(0, timer_base + TPM_SC); + writel(0, timer_base + TPM_CNT); + writel(0, timer_base + TPM_C0SC); + + /* increase per cnt, div 8 by default */ + writel(TPM_SC_CMOD_INC_PER_CNT | TPM_SC_CMOD_DIV_DEFAULT, + timer_base + TPM_SC); + + /* set MOD register to maximum for free running mode */ + writel(0xffffffff, timer_base + TPM_MOD); + + rate = clk_get_rate(per) >> 3; + ret = tpm_clocksource_init(rate); + if (ret) + goto err_per_clk_enable; + + ret = tpm_clockevent_init(rate, irq); + if (ret) + goto err_per_clk_enable; + + return 0; + +err_per_clk_enable: + clk_disable_unprepare(ipg); +err_clk_get: + clk_put(per); + clk_put(ipg); +err_iomap: + iounmap(timer_base); + return ret; +} +TIMER_OF_DECLARE(imx7ulp, "fsl,imx7ulp-tpm", tpm_timer_init); diff --git a/drivers/clocksource/timer-of.c b/drivers/clocksource/timer-of.c index 4d7aef9d9c15..c79122d8e10d 100644 --- a/drivers/clocksource/timer-of.c +++ b/drivers/clocksource/timer-of.c @@ -52,7 +52,7 @@ static __init int timer_irq_init(struct device_node *np, of_irq->irq = irq_of_parse_and_map(np, of_irq->index); } if (!of_irq->irq) { - pr_err("Failed to map interrupt for %s\n", np->full_name); + pr_err("Failed to map interrupt for %pOF\n", np); return -EINVAL; } @@ -63,8 +63,7 @@ static __init int timer_irq_init(struct device_node *np, of_irq->flags ? of_irq->flags : IRQF_TIMER, np->full_name, clkevt); if (ret) { - pr_err("Failed to request irq %d for %s\n", of_irq->irq, - np->full_name); + pr_err("Failed to request irq %d for %pOF\n", of_irq->irq, np); return ret; } @@ -88,20 +87,20 @@ static __init int timer_clk_init(struct device_node *np, of_clk->clk = of_clk->name ? of_clk_get_by_name(np, of_clk->name) : of_clk_get(np, of_clk->index); if (IS_ERR(of_clk->clk)) { - pr_err("Failed to get clock for %s\n", np->full_name); + pr_err("Failed to get clock for %pOF\n", np); return PTR_ERR(of_clk->clk); } ret = clk_prepare_enable(of_clk->clk); if (ret) { - pr_err("Failed for enable clock for %s\n", np->full_name); + pr_err("Failed for enable clock for %pOF\n", np); goto out_clk_put; } of_clk->rate = clk_get_rate(of_clk->clk); if (!of_clk->rate) { ret = -EINVAL; - pr_err("Failed to get clock rate for %s\n", np->full_name); + pr_err("Failed to get clock rate for %pOF\n", np); goto out_clk_disable; } diff --git a/drivers/clocksource/timer-probe.c b/drivers/clocksource/timer-probe.c index da81e5de74fe..028075720334 100644 --- a/drivers/clocksource/timer-probe.c +++ b/drivers/clocksource/timer-probe.c @@ -40,8 +40,7 @@ void __init timer_probe(void) ret = init_func_ret(np); if (ret) { - pr_err("Failed to initialize '%s': %d\n", - of_node_full_name(np), ret); + pr_err("Failed to initialize '%pOF': %d\n", np, ret); continue; } diff --git a/drivers/clocksource/timer-stm32.c b/drivers/clocksource/timer-stm32.c index 174d1243ea93..8f2423789ba9 100644 --- a/drivers/clocksource/timer-stm32.c +++ b/drivers/clocksource/timer-stm32.c @@ -138,7 +138,7 @@ static int __init stm32_clockevent_init(struct device_node *np) irq = irq_of_parse_and_map(np, 0); if (!irq) { ret = -EINVAL; - pr_err("%s: failed to get irq.\n", np->full_name); + pr_err("%pOF: failed to get irq.\n", np); goto err_get_irq; } @@ -168,12 +168,12 @@ static int __init stm32_clockevent_init(struct device_node *np) 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); + pr_err("%pOF: failed to request irq.\n", np); goto err_get_irq; } - pr_info("%s: STM32 clockevent driver initialized (%d bits)\n", - np->full_name, bits); + pr_info("%pOF: STM32 clockevent driver initialized (%d bits)\n", + np, bits); return ret; |