162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Freescale FlexTimer Module (FTM) timer driver. 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright 2014 Freescale Semiconductor, Inc. 662306a36Sopenharmony_ci */ 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include <linux/clk.h> 962306a36Sopenharmony_ci#include <linux/clockchips.h> 1062306a36Sopenharmony_ci#include <linux/clocksource.h> 1162306a36Sopenharmony_ci#include <linux/err.h> 1262306a36Sopenharmony_ci#include <linux/interrupt.h> 1362306a36Sopenharmony_ci#include <linux/io.h> 1462306a36Sopenharmony_ci#include <linux/of_address.h> 1562306a36Sopenharmony_ci#include <linux/of_irq.h> 1662306a36Sopenharmony_ci#include <linux/sched_clock.h> 1762306a36Sopenharmony_ci#include <linux/slab.h> 1862306a36Sopenharmony_ci#include <linux/fsl/ftm.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_MASK_SHIFT) 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_cistruct ftm_clock_device { 2362306a36Sopenharmony_ci void __iomem *clksrc_base; 2462306a36Sopenharmony_ci void __iomem *clkevt_base; 2562306a36Sopenharmony_ci unsigned long periodic_cyc; 2662306a36Sopenharmony_ci unsigned long ps; 2762306a36Sopenharmony_ci bool big_endian; 2862306a36Sopenharmony_ci}; 2962306a36Sopenharmony_ci 3062306a36Sopenharmony_cistatic struct ftm_clock_device *priv; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic inline u32 ftm_readl(void __iomem *addr) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci if (priv->big_endian) 3562306a36Sopenharmony_ci return ioread32be(addr); 3662306a36Sopenharmony_ci else 3762306a36Sopenharmony_ci return ioread32(addr); 3862306a36Sopenharmony_ci} 3962306a36Sopenharmony_ci 4062306a36Sopenharmony_cistatic inline void ftm_writel(u32 val, void __iomem *addr) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci if (priv->big_endian) 4362306a36Sopenharmony_ci iowrite32be(val, addr); 4462306a36Sopenharmony_ci else 4562306a36Sopenharmony_ci iowrite32(val, addr); 4662306a36Sopenharmony_ci} 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_cistatic inline void ftm_counter_enable(void __iomem *base) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci u32 val; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci /* select and enable counter clock source */ 5362306a36Sopenharmony_ci val = ftm_readl(base + FTM_SC); 5462306a36Sopenharmony_ci val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); 5562306a36Sopenharmony_ci val |= priv->ps | FTM_SC_CLK(1); 5662306a36Sopenharmony_ci ftm_writel(val, base + FTM_SC); 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_cistatic inline void ftm_counter_disable(void __iomem *base) 6062306a36Sopenharmony_ci{ 6162306a36Sopenharmony_ci u32 val; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci /* disable counter clock source */ 6462306a36Sopenharmony_ci val = ftm_readl(base + FTM_SC); 6562306a36Sopenharmony_ci val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK); 6662306a36Sopenharmony_ci ftm_writel(val, base + FTM_SC); 6762306a36Sopenharmony_ci} 6862306a36Sopenharmony_ci 6962306a36Sopenharmony_cistatic inline void ftm_irq_acknowledge(void __iomem *base) 7062306a36Sopenharmony_ci{ 7162306a36Sopenharmony_ci u32 val; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci val = ftm_readl(base + FTM_SC); 7462306a36Sopenharmony_ci val &= ~FTM_SC_TOF; 7562306a36Sopenharmony_ci ftm_writel(val, base + FTM_SC); 7662306a36Sopenharmony_ci} 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistatic inline void ftm_irq_enable(void __iomem *base) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci u32 val; 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci val = ftm_readl(base + FTM_SC); 8362306a36Sopenharmony_ci val |= FTM_SC_TOIE; 8462306a36Sopenharmony_ci ftm_writel(val, base + FTM_SC); 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic inline void ftm_irq_disable(void __iomem *base) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci u32 val; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci val = ftm_readl(base + FTM_SC); 9262306a36Sopenharmony_ci val &= ~FTM_SC_TOIE; 9362306a36Sopenharmony_ci ftm_writel(val, base + FTM_SC); 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic inline void ftm_reset_counter(void __iomem *base) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci /* 9962306a36Sopenharmony_ci * The CNT register contains the FTM counter value. 10062306a36Sopenharmony_ci * Reset clears the CNT register. Writing any value to COUNT 10162306a36Sopenharmony_ci * updates the counter with its initial value, CNTIN. 10262306a36Sopenharmony_ci */ 10362306a36Sopenharmony_ci ftm_writel(0x00, base + FTM_CNT); 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistatic u64 notrace ftm_read_sched_clock(void) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci return ftm_readl(priv->clksrc_base + FTM_CNT); 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic int ftm_set_next_event(unsigned long delta, 11262306a36Sopenharmony_ci struct clock_event_device *unused) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci /* 11562306a36Sopenharmony_ci * The CNNIN and MOD are all double buffer registers, writing 11662306a36Sopenharmony_ci * to the MOD register latches the value into a buffer. The MOD 11762306a36Sopenharmony_ci * register is updated with the value of its write buffer with 11862306a36Sopenharmony_ci * the following scenario: 11962306a36Sopenharmony_ci * a, the counter source clock is disabled. 12062306a36Sopenharmony_ci */ 12162306a36Sopenharmony_ci ftm_counter_disable(priv->clkevt_base); 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci /* Force the value of CNTIN to be loaded into the FTM counter */ 12462306a36Sopenharmony_ci ftm_reset_counter(priv->clkevt_base); 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci /* 12762306a36Sopenharmony_ci * The counter increments until the value of MOD is reached, 12862306a36Sopenharmony_ci * at which point the counter is reloaded with the value of CNTIN. 12962306a36Sopenharmony_ci * The TOF (the overflow flag) bit is set when the FTM counter 13062306a36Sopenharmony_ci * changes from MOD to CNTIN. So we should using the delta - 1. 13162306a36Sopenharmony_ci */ 13262306a36Sopenharmony_ci ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD); 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_ci ftm_counter_enable(priv->clkevt_base); 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci ftm_irq_enable(priv->clkevt_base); 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci return 0; 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic int ftm_set_oneshot(struct clock_event_device *evt) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci ftm_counter_disable(priv->clkevt_base); 14462306a36Sopenharmony_ci return 0; 14562306a36Sopenharmony_ci} 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_cistatic int ftm_set_periodic(struct clock_event_device *evt) 14862306a36Sopenharmony_ci{ 14962306a36Sopenharmony_ci ftm_set_next_event(priv->periodic_cyc, evt); 15062306a36Sopenharmony_ci return 0; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistatic irqreturn_t ftm_evt_interrupt(int irq, void *dev_id) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci struct clock_event_device *evt = dev_id; 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci ftm_irq_acknowledge(priv->clkevt_base); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci if (likely(clockevent_state_oneshot(evt))) { 16062306a36Sopenharmony_ci ftm_irq_disable(priv->clkevt_base); 16162306a36Sopenharmony_ci ftm_counter_disable(priv->clkevt_base); 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci evt->event_handler(evt); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci return IRQ_HANDLED; 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_cistatic struct clock_event_device ftm_clockevent = { 17062306a36Sopenharmony_ci .name = "Freescale ftm timer", 17162306a36Sopenharmony_ci .features = CLOCK_EVT_FEAT_PERIODIC | 17262306a36Sopenharmony_ci CLOCK_EVT_FEAT_ONESHOT, 17362306a36Sopenharmony_ci .set_state_periodic = ftm_set_periodic, 17462306a36Sopenharmony_ci .set_state_oneshot = ftm_set_oneshot, 17562306a36Sopenharmony_ci .set_next_event = ftm_set_next_event, 17662306a36Sopenharmony_ci .rating = 300, 17762306a36Sopenharmony_ci}; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_cistatic int __init ftm_clockevent_init(unsigned long freq, int irq) 18062306a36Sopenharmony_ci{ 18162306a36Sopenharmony_ci int err; 18262306a36Sopenharmony_ci 18362306a36Sopenharmony_ci ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN); 18462306a36Sopenharmony_ci ftm_writel(~0u, priv->clkevt_base + FTM_MOD); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci ftm_reset_counter(priv->clkevt_base); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci err = request_irq(irq, ftm_evt_interrupt, IRQF_TIMER | IRQF_IRQPOLL, 18962306a36Sopenharmony_ci "Freescale ftm timer", &ftm_clockevent); 19062306a36Sopenharmony_ci if (err) { 19162306a36Sopenharmony_ci pr_err("ftm: setup irq failed: %d\n", err); 19262306a36Sopenharmony_ci return err; 19362306a36Sopenharmony_ci } 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci ftm_clockevent.cpumask = cpumask_of(0); 19662306a36Sopenharmony_ci ftm_clockevent.irq = irq; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci clockevents_config_and_register(&ftm_clockevent, 19962306a36Sopenharmony_ci freq / (1 << priv->ps), 20062306a36Sopenharmony_ci 1, 0xffff); 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci ftm_counter_enable(priv->clkevt_base); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci return 0; 20562306a36Sopenharmony_ci} 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_cistatic int __init ftm_clocksource_init(unsigned long freq) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci int err; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN); 21262306a36Sopenharmony_ci ftm_writel(~0u, priv->clksrc_base + FTM_MOD); 21362306a36Sopenharmony_ci 21462306a36Sopenharmony_ci ftm_reset_counter(priv->clksrc_base); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps)); 21762306a36Sopenharmony_ci err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm", 21862306a36Sopenharmony_ci freq / (1 << priv->ps), 300, 16, 21962306a36Sopenharmony_ci clocksource_mmio_readl_up); 22062306a36Sopenharmony_ci if (err) { 22162306a36Sopenharmony_ci pr_err("ftm: init clock source mmio failed: %d\n", err); 22262306a36Sopenharmony_ci return err; 22362306a36Sopenharmony_ci } 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci ftm_counter_enable(priv->clksrc_base); 22662306a36Sopenharmony_ci 22762306a36Sopenharmony_ci return 0; 22862306a36Sopenharmony_ci} 22962306a36Sopenharmony_ci 23062306a36Sopenharmony_cistatic int __init __ftm_clk_init(struct device_node *np, char *cnt_name, 23162306a36Sopenharmony_ci char *ftm_name) 23262306a36Sopenharmony_ci{ 23362306a36Sopenharmony_ci struct clk *clk; 23462306a36Sopenharmony_ci int err; 23562306a36Sopenharmony_ci 23662306a36Sopenharmony_ci clk = of_clk_get_by_name(np, cnt_name); 23762306a36Sopenharmony_ci if (IS_ERR(clk)) { 23862306a36Sopenharmony_ci pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk)); 23962306a36Sopenharmony_ci return PTR_ERR(clk); 24062306a36Sopenharmony_ci } 24162306a36Sopenharmony_ci err = clk_prepare_enable(clk); 24262306a36Sopenharmony_ci if (err) { 24362306a36Sopenharmony_ci pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n", 24462306a36Sopenharmony_ci cnt_name, err); 24562306a36Sopenharmony_ci return err; 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci 24862306a36Sopenharmony_ci clk = of_clk_get_by_name(np, ftm_name); 24962306a36Sopenharmony_ci if (IS_ERR(clk)) { 25062306a36Sopenharmony_ci pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk)); 25162306a36Sopenharmony_ci return PTR_ERR(clk); 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci err = clk_prepare_enable(clk); 25462306a36Sopenharmony_ci if (err) 25562306a36Sopenharmony_ci pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n", 25662306a36Sopenharmony_ci ftm_name, err); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci return clk_get_rate(clk); 25962306a36Sopenharmony_ci} 26062306a36Sopenharmony_ci 26162306a36Sopenharmony_cistatic unsigned long __init ftm_clk_init(struct device_node *np) 26262306a36Sopenharmony_ci{ 26362306a36Sopenharmony_ci long freq; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_ci freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt"); 26662306a36Sopenharmony_ci if (freq <= 0) 26762306a36Sopenharmony_ci return 0; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src"); 27062306a36Sopenharmony_ci if (freq <= 0) 27162306a36Sopenharmony_ci return 0; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci return freq; 27462306a36Sopenharmony_ci} 27562306a36Sopenharmony_ci 27662306a36Sopenharmony_cistatic int __init ftm_calc_closest_round_cyc(unsigned long freq) 27762306a36Sopenharmony_ci{ 27862306a36Sopenharmony_ci priv->ps = 0; 27962306a36Sopenharmony_ci 28062306a36Sopenharmony_ci /* The counter register is only using the lower 16 bits, and 28162306a36Sopenharmony_ci * if the 'freq' value is to big here, then the periodic_cyc 28262306a36Sopenharmony_ci * may exceed 0xFFFF. 28362306a36Sopenharmony_ci */ 28462306a36Sopenharmony_ci do { 28562306a36Sopenharmony_ci priv->periodic_cyc = DIV_ROUND_CLOSEST(freq, 28662306a36Sopenharmony_ci HZ * (1 << priv->ps++)); 28762306a36Sopenharmony_ci } while (priv->periodic_cyc > 0xFFFF); 28862306a36Sopenharmony_ci 28962306a36Sopenharmony_ci if (priv->ps > FTM_PS_MAX) { 29062306a36Sopenharmony_ci pr_err("ftm: the prescaler is %lu > %d\n", 29162306a36Sopenharmony_ci priv->ps, FTM_PS_MAX); 29262306a36Sopenharmony_ci return -EINVAL; 29362306a36Sopenharmony_ci } 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci return 0; 29662306a36Sopenharmony_ci} 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_cistatic int __init ftm_timer_init(struct device_node *np) 29962306a36Sopenharmony_ci{ 30062306a36Sopenharmony_ci unsigned long freq; 30162306a36Sopenharmony_ci int ret, irq; 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci priv = kzalloc(sizeof(*priv), GFP_KERNEL); 30462306a36Sopenharmony_ci if (!priv) 30562306a36Sopenharmony_ci return -ENOMEM; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci ret = -ENXIO; 30862306a36Sopenharmony_ci priv->clkevt_base = of_iomap(np, 0); 30962306a36Sopenharmony_ci if (!priv->clkevt_base) { 31062306a36Sopenharmony_ci pr_err("ftm: unable to map event timer registers\n"); 31162306a36Sopenharmony_ci goto err_clkevt; 31262306a36Sopenharmony_ci } 31362306a36Sopenharmony_ci 31462306a36Sopenharmony_ci priv->clksrc_base = of_iomap(np, 1); 31562306a36Sopenharmony_ci if (!priv->clksrc_base) { 31662306a36Sopenharmony_ci pr_err("ftm: unable to map source timer registers\n"); 31762306a36Sopenharmony_ci goto err_clksrc; 31862306a36Sopenharmony_ci } 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci ret = -EINVAL; 32162306a36Sopenharmony_ci irq = irq_of_parse_and_map(np, 0); 32262306a36Sopenharmony_ci if (irq <= 0) { 32362306a36Sopenharmony_ci pr_err("ftm: unable to get IRQ from DT, %d\n", irq); 32462306a36Sopenharmony_ci goto err; 32562306a36Sopenharmony_ci } 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci priv->big_endian = of_property_read_bool(np, "big-endian"); 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci freq = ftm_clk_init(np); 33062306a36Sopenharmony_ci if (!freq) 33162306a36Sopenharmony_ci goto err; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci ret = ftm_calc_closest_round_cyc(freq); 33462306a36Sopenharmony_ci if (ret) 33562306a36Sopenharmony_ci goto err; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci ret = ftm_clocksource_init(freq); 33862306a36Sopenharmony_ci if (ret) 33962306a36Sopenharmony_ci goto err; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci ret = ftm_clockevent_init(freq, irq); 34262306a36Sopenharmony_ci if (ret) 34362306a36Sopenharmony_ci goto err; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci return 0; 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_cierr: 34862306a36Sopenharmony_ci iounmap(priv->clksrc_base); 34962306a36Sopenharmony_cierr_clksrc: 35062306a36Sopenharmony_ci iounmap(priv->clkevt_base); 35162306a36Sopenharmony_cierr_clkevt: 35262306a36Sopenharmony_ci kfree(priv); 35362306a36Sopenharmony_ci return ret; 35462306a36Sopenharmony_ci} 35562306a36Sopenharmony_ciTIMER_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init); 356