mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-25 02:09:42 +00:00
![Daniel Lezcano](/assets/img/avatar_default.png)
The CLOCKSOURCE_OF_DECLARE macro is used widely for the timers to declare the clocksource at early stage. However, this macro is also used to initialize the clockevent if any, or the clockevent only. It was originally suggested to declare another macro to initialize a clockevent, so in order to separate the two entities even they belong to the same IP. This was not accepted because of the impact on the DT where splitting a clocksource/clockevent definition does not make sense as it is a Linux concept not a hardware description. On the other side, the clocksource has not interrupt declared while the clockevent has, so it is easy from the driver to know if the description is for a clockevent or a clocksource, IOW it could be implemented at the driver level. So instead of dealing with a named clocksource macro, let's use a more generic one: TIMER_OF_DECLARE. The patch has not functional changes. Signed-off-by: Daniel Lezcano <daniel.lezcano@linaro.org> Acked-by: Heiko Stuebner <heiko@sntech.de> Acked-by: Neil Armstrong <narmstrong@baylibre.com> Acked-by: Arnd Bergmann <arnd@arndb.de> Acked-by: Matthias Brugger <matthias.bgg@gmail.com> Reviewed-by: Linus Walleij <linus.walleij@linaro.org>
372 lines
8.2 KiB
C
372 lines
8.2 KiB
C
/*
|
|
* Freescale FlexTimer Module (FTM) timer driver.
|
|
*
|
|
* Copyright 2014 Freescale Semiconductor, Inc.
|
|
*
|
|
* 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/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/io.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/sched_clock.h>
|
|
#include <linux/slab.h>
|
|
|
|
#define FTM_SC 0x00
|
|
#define FTM_SC_CLK_SHIFT 3
|
|
#define FTM_SC_CLK_MASK (0x3 << FTM_SC_CLK_SHIFT)
|
|
#define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_SHIFT)
|
|
#define FTM_SC_PS_MASK 0x7
|
|
#define FTM_SC_TOIE BIT(6)
|
|
#define FTM_SC_TOF BIT(7)
|
|
|
|
#define FTM_CNT 0x04
|
|
#define FTM_MOD 0x08
|
|
#define FTM_CNTIN 0x4C
|
|
|
|
#define FTM_PS_MAX 7
|
|
|
|
struct ftm_clock_device {
|
|
void __iomem *clksrc_base;
|
|
void __iomem *clkevt_base;
|
|
unsigned long periodic_cyc;
|
|
unsigned long ps;
|
|
bool big_endian;
|
|
};
|
|
|
|
static struct ftm_clock_device *priv;
|
|
|
|
static inline u32 ftm_readl(void __iomem *addr)
|
|
{
|
|
if (priv->big_endian)
|
|
return ioread32be(addr);
|
|
else
|
|
return ioread32(addr);
|
|
}
|
|
|
|
static inline void ftm_writel(u32 val, void __iomem *addr)
|
|
{
|
|
if (priv->big_endian)
|
|
iowrite32be(val, addr);
|
|
else
|
|
iowrite32(val, addr);
|
|
}
|
|
|
|
static inline void ftm_counter_enable(void __iomem *base)
|
|
{
|
|
u32 val;
|
|
|
|
/* select and enable counter clock source */
|
|
val = ftm_readl(base + FTM_SC);
|
|
val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
|
|
val |= priv->ps | FTM_SC_CLK(1);
|
|
ftm_writel(val, base + FTM_SC);
|
|
}
|
|
|
|
static inline void ftm_counter_disable(void __iomem *base)
|
|
{
|
|
u32 val;
|
|
|
|
/* disable counter clock source */
|
|
val = ftm_readl(base + FTM_SC);
|
|
val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
|
|
ftm_writel(val, base + FTM_SC);
|
|
}
|
|
|
|
static inline void ftm_irq_acknowledge(void __iomem *base)
|
|
{
|
|
u32 val;
|
|
|
|
val = ftm_readl(base + FTM_SC);
|
|
val &= ~FTM_SC_TOF;
|
|
ftm_writel(val, base + FTM_SC);
|
|
}
|
|
|
|
static inline void ftm_irq_enable(void __iomem *base)
|
|
{
|
|
u32 val;
|
|
|
|
val = ftm_readl(base + FTM_SC);
|
|
val |= FTM_SC_TOIE;
|
|
ftm_writel(val, base + FTM_SC);
|
|
}
|
|
|
|
static inline void ftm_irq_disable(void __iomem *base)
|
|
{
|
|
u32 val;
|
|
|
|
val = ftm_readl(base + FTM_SC);
|
|
val &= ~FTM_SC_TOIE;
|
|
ftm_writel(val, base + FTM_SC);
|
|
}
|
|
|
|
static inline void ftm_reset_counter(void __iomem *base)
|
|
{
|
|
/*
|
|
* The CNT register contains the FTM counter value.
|
|
* Reset clears the CNT register. Writing any value to COUNT
|
|
* updates the counter with its initial value, CNTIN.
|
|
*/
|
|
ftm_writel(0x00, base + FTM_CNT);
|
|
}
|
|
|
|
static u64 notrace ftm_read_sched_clock(void)
|
|
{
|
|
return ftm_readl(priv->clksrc_base + FTM_CNT);
|
|
}
|
|
|
|
static int ftm_set_next_event(unsigned long delta,
|
|
struct clock_event_device *unused)
|
|
{
|
|
/*
|
|
* The CNNIN and MOD are all double buffer registers, writing
|
|
* to the MOD register latches the value into a buffer. The MOD
|
|
* register is updated with the value of its write buffer with
|
|
* the following scenario:
|
|
* a, the counter source clock is diabled.
|
|
*/
|
|
ftm_counter_disable(priv->clkevt_base);
|
|
|
|
/* Force the value of CNTIN to be loaded into the FTM counter */
|
|
ftm_reset_counter(priv->clkevt_base);
|
|
|
|
/*
|
|
* The counter increments until the value of MOD is reached,
|
|
* at which point the counter is reloaded with the value of CNTIN.
|
|
* The TOF (the overflow flag) bit is set when the FTM counter
|
|
* changes from MOD to CNTIN. So we should using the delta - 1.
|
|
*/
|
|
ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD);
|
|
|
|
ftm_counter_enable(priv->clkevt_base);
|
|
|
|
ftm_irq_enable(priv->clkevt_base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ftm_set_oneshot(struct clock_event_device *evt)
|
|
{
|
|
ftm_counter_disable(priv->clkevt_base);
|
|
return 0;
|
|
}
|
|
|
|
static int ftm_set_periodic(struct clock_event_device *evt)
|
|
{
|
|
ftm_set_next_event(priv->periodic_cyc, evt);
|
|
return 0;
|
|
}
|
|
|
|
static irqreturn_t ftm_evt_interrupt(int irq, void *dev_id)
|
|
{
|
|
struct clock_event_device *evt = dev_id;
|
|
|
|
ftm_irq_acknowledge(priv->clkevt_base);
|
|
|
|
if (likely(clockevent_state_oneshot(evt))) {
|
|
ftm_irq_disable(priv->clkevt_base);
|
|
ftm_counter_disable(priv->clkevt_base);
|
|
}
|
|
|
|
evt->event_handler(evt);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static struct clock_event_device ftm_clockevent = {
|
|
.name = "Freescale ftm timer",
|
|
.features = CLOCK_EVT_FEAT_PERIODIC |
|
|
CLOCK_EVT_FEAT_ONESHOT,
|
|
.set_state_periodic = ftm_set_periodic,
|
|
.set_state_oneshot = ftm_set_oneshot,
|
|
.set_next_event = ftm_set_next_event,
|
|
.rating = 300,
|
|
};
|
|
|
|
static struct irqaction ftm_timer_irq = {
|
|
.name = "Freescale ftm timer",
|
|
.flags = IRQF_TIMER | IRQF_IRQPOLL,
|
|
.handler = ftm_evt_interrupt,
|
|
.dev_id = &ftm_clockevent,
|
|
};
|
|
|
|
static int __init ftm_clockevent_init(unsigned long freq, int irq)
|
|
{
|
|
int err;
|
|
|
|
ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN);
|
|
ftm_writel(~0u, priv->clkevt_base + FTM_MOD);
|
|
|
|
ftm_reset_counter(priv->clkevt_base);
|
|
|
|
err = setup_irq(irq, &ftm_timer_irq);
|
|
if (err) {
|
|
pr_err("ftm: setup irq failed: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
ftm_clockevent.cpumask = cpumask_of(0);
|
|
ftm_clockevent.irq = irq;
|
|
|
|
clockevents_config_and_register(&ftm_clockevent,
|
|
freq / (1 << priv->ps),
|
|
1, 0xffff);
|
|
|
|
ftm_counter_enable(priv->clkevt_base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init ftm_clocksource_init(unsigned long freq)
|
|
{
|
|
int err;
|
|
|
|
ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN);
|
|
ftm_writel(~0u, priv->clksrc_base + FTM_MOD);
|
|
|
|
ftm_reset_counter(priv->clksrc_base);
|
|
|
|
sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps));
|
|
err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm",
|
|
freq / (1 << priv->ps), 300, 16,
|
|
clocksource_mmio_readl_up);
|
|
if (err) {
|
|
pr_err("ftm: init clock source mmio failed: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
ftm_counter_enable(priv->clksrc_base);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init __ftm_clk_init(struct device_node *np, char *cnt_name,
|
|
char *ftm_name)
|
|
{
|
|
struct clk *clk;
|
|
int err;
|
|
|
|
clk = of_clk_get_by_name(np, cnt_name);
|
|
if (IS_ERR(clk)) {
|
|
pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk));
|
|
return PTR_ERR(clk);
|
|
}
|
|
err = clk_prepare_enable(clk);
|
|
if (err) {
|
|
pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
|
|
cnt_name, err);
|
|
return err;
|
|
}
|
|
|
|
clk = of_clk_get_by_name(np, ftm_name);
|
|
if (IS_ERR(clk)) {
|
|
pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk));
|
|
return PTR_ERR(clk);
|
|
}
|
|
err = clk_prepare_enable(clk);
|
|
if (err)
|
|
pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
|
|
ftm_name, err);
|
|
|
|
return clk_get_rate(clk);
|
|
}
|
|
|
|
static unsigned long __init ftm_clk_init(struct device_node *np)
|
|
{
|
|
unsigned long freq;
|
|
|
|
freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt");
|
|
if (freq <= 0)
|
|
return 0;
|
|
|
|
freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src");
|
|
if (freq <= 0)
|
|
return 0;
|
|
|
|
return freq;
|
|
}
|
|
|
|
static int __init ftm_calc_closest_round_cyc(unsigned long freq)
|
|
{
|
|
priv->ps = 0;
|
|
|
|
/* The counter register is only using the lower 16 bits, and
|
|
* if the 'freq' value is to big here, then the periodic_cyc
|
|
* may exceed 0xFFFF.
|
|
*/
|
|
do {
|
|
priv->periodic_cyc = DIV_ROUND_CLOSEST(freq,
|
|
HZ * (1 << priv->ps++));
|
|
} while (priv->periodic_cyc > 0xFFFF);
|
|
|
|
if (priv->ps > FTM_PS_MAX) {
|
|
pr_err("ftm: the prescaler is %lu > %d\n",
|
|
priv->ps, FTM_PS_MAX);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __init ftm_timer_init(struct device_node *np)
|
|
{
|
|
unsigned long freq;
|
|
int ret, irq;
|
|
|
|
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
|
if (!priv)
|
|
return -ENOMEM;
|
|
|
|
ret = -ENXIO;
|
|
priv->clkevt_base = of_iomap(np, 0);
|
|
if (!priv->clkevt_base) {
|
|
pr_err("ftm: unable to map event timer registers\n");
|
|
goto err;
|
|
}
|
|
|
|
priv->clksrc_base = of_iomap(np, 1);
|
|
if (!priv->clksrc_base) {
|
|
pr_err("ftm: unable to map source timer registers\n");
|
|
goto err;
|
|
}
|
|
|
|
ret = -EINVAL;
|
|
irq = irq_of_parse_and_map(np, 0);
|
|
if (irq <= 0) {
|
|
pr_err("ftm: unable to get IRQ from DT, %d\n", irq);
|
|
goto err;
|
|
}
|
|
|
|
priv->big_endian = of_property_read_bool(np, "big-endian");
|
|
|
|
freq = ftm_clk_init(np);
|
|
if (!freq)
|
|
goto err;
|
|
|
|
ret = ftm_calc_closest_round_cyc(freq);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = ftm_clocksource_init(freq);
|
|
if (ret)
|
|
goto err;
|
|
|
|
ret = ftm_clockevent_init(freq, irq);
|
|
if (ret)
|
|
goto err;
|
|
|
|
return 0;
|
|
|
|
err:
|
|
kfree(priv);
|
|
return ret;
|
|
}
|
|
TIMER_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init);
|