brintos

brintos / linux-shallow public Read only

0
0
Text · 7.7 KiB · 93f336e Raw
356 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Freescale FlexTimer Module (FTM) timer driver.4 *5 * Copyright 2014 Freescale Semiconductor, Inc.6 */7 8#include <linux/clk.h>9#include <linux/clockchips.h>10#include <linux/clocksource.h>11#include <linux/err.h>12#include <linux/interrupt.h>13#include <linux/io.h>14#include <linux/of_address.h>15#include <linux/of_irq.h>16#include <linux/sched_clock.h>17#include <linux/slab.h>18#include <linux/fsl/ftm.h>19 20#define FTM_SC_CLK(c)	((c) << FTM_SC_CLK_MASK_SHIFT)21 22struct ftm_clock_device {23	void __iomem *clksrc_base;24	void __iomem *clkevt_base;25	unsigned long periodic_cyc;26	unsigned long ps;27	bool big_endian;28};29 30static struct ftm_clock_device *priv;31 32static inline u32 ftm_readl(void __iomem *addr)33{34	if (priv->big_endian)35		return ioread32be(addr);36	else37		return ioread32(addr);38}39 40static inline void ftm_writel(u32 val, void __iomem *addr)41{42	if (priv->big_endian)43		iowrite32be(val, addr);44	else45		iowrite32(val, addr);46}47 48static inline void ftm_counter_enable(void __iomem *base)49{50	u32 val;51 52	/* select and enable counter clock source */53	val = ftm_readl(base + FTM_SC);54	val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);55	val |= priv->ps | FTM_SC_CLK(1);56	ftm_writel(val, base + FTM_SC);57}58 59static inline void ftm_counter_disable(void __iomem *base)60{61	u32 val;62 63	/* disable counter clock source */64	val = ftm_readl(base + FTM_SC);65	val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);66	ftm_writel(val, base + FTM_SC);67}68 69static inline void ftm_irq_acknowledge(void __iomem *base)70{71	u32 val;72 73	val = ftm_readl(base + FTM_SC);74	val &= ~FTM_SC_TOF;75	ftm_writel(val, base + FTM_SC);76}77 78static inline void ftm_irq_enable(void __iomem *base)79{80	u32 val;81 82	val = ftm_readl(base + FTM_SC);83	val |= FTM_SC_TOIE;84	ftm_writel(val, base + FTM_SC);85}86 87static inline void ftm_irq_disable(void __iomem *base)88{89	u32 val;90 91	val = ftm_readl(base + FTM_SC);92	val &= ~FTM_SC_TOIE;93	ftm_writel(val, base + FTM_SC);94}95 96static inline void ftm_reset_counter(void __iomem *base)97{98	/*99	 * The CNT register contains the FTM counter value.100	 * Reset clears the CNT register. Writing any value to COUNT101	 * updates the counter with its initial value, CNTIN.102	 */103	ftm_writel(0x00, base + FTM_CNT);104}105 106static u64 notrace ftm_read_sched_clock(void)107{108	return ftm_readl(priv->clksrc_base + FTM_CNT);109}110 111static int ftm_set_next_event(unsigned long delta,112				struct clock_event_device *unused)113{114	/*115	 * The CNNIN and MOD are all double buffer registers, writing116	 * to the MOD register latches the value into a buffer. The MOD117	 * register is updated with the value of its write buffer with118	 * the following scenario:119	 * a, the counter source clock is disabled.120	 */121	ftm_counter_disable(priv->clkevt_base);122 123	/* Force the value of CNTIN to be loaded into the FTM counter */124	ftm_reset_counter(priv->clkevt_base);125 126	/*127	 * The counter increments until the value of MOD is reached,128	 * at which point the counter is reloaded with the value of CNTIN.129	 * The TOF (the overflow flag) bit is set when the FTM counter130	 * changes from MOD to CNTIN. So we should using the delta - 1.131	 */132	ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD);133 134	ftm_counter_enable(priv->clkevt_base);135 136	ftm_irq_enable(priv->clkevt_base);137 138	return 0;139}140 141static int ftm_set_oneshot(struct clock_event_device *evt)142{143	ftm_counter_disable(priv->clkevt_base);144	return 0;145}146 147static int ftm_set_periodic(struct clock_event_device *evt)148{149	ftm_set_next_event(priv->periodic_cyc, evt);150	return 0;151}152 153static irqreturn_t ftm_evt_interrupt(int irq, void *dev_id)154{155	struct clock_event_device *evt = dev_id;156 157	ftm_irq_acknowledge(priv->clkevt_base);158 159	if (likely(clockevent_state_oneshot(evt))) {160		ftm_irq_disable(priv->clkevt_base);161		ftm_counter_disable(priv->clkevt_base);162	}163 164	evt->event_handler(evt);165 166	return IRQ_HANDLED;167}168 169static struct clock_event_device ftm_clockevent = {170	.name			= "Freescale ftm timer",171	.features		= CLOCK_EVT_FEAT_PERIODIC |172				  CLOCK_EVT_FEAT_ONESHOT,173	.set_state_periodic	= ftm_set_periodic,174	.set_state_oneshot	= ftm_set_oneshot,175	.set_next_event		= ftm_set_next_event,176	.rating			= 300,177};178 179static int __init ftm_clockevent_init(unsigned long freq, int irq)180{181	int err;182 183	ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN);184	ftm_writel(~0u, priv->clkevt_base + FTM_MOD);185 186	ftm_reset_counter(priv->clkevt_base);187 188	err = request_irq(irq, ftm_evt_interrupt, IRQF_TIMER | IRQF_IRQPOLL,189			  "Freescale ftm timer", &ftm_clockevent);190	if (err) {191		pr_err("ftm: setup irq failed: %d\n", err);192		return err;193	}194 195	ftm_clockevent.cpumask = cpumask_of(0);196	ftm_clockevent.irq = irq;197 198	clockevents_config_and_register(&ftm_clockevent,199					freq / (1 << priv->ps),200					1, 0xffff);201 202	ftm_counter_enable(priv->clkevt_base);203 204	return 0;205}206 207static int __init ftm_clocksource_init(unsigned long freq)208{209	int err;210 211	ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN);212	ftm_writel(~0u, priv->clksrc_base + FTM_MOD);213 214	ftm_reset_counter(priv->clksrc_base);215 216	sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps));217	err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm",218				    freq / (1 << priv->ps), 300, 16,219				    clocksource_mmio_readl_up);220	if (err) {221		pr_err("ftm: init clock source mmio failed: %d\n", err);222		return err;223	}224 225	ftm_counter_enable(priv->clksrc_base);226 227	return 0;228}229 230static int __init __ftm_clk_init(struct device_node *np, char *cnt_name,231				 char *ftm_name)232{233	struct clk *clk;234	int err;235 236	clk = of_clk_get_by_name(np, cnt_name);237	if (IS_ERR(clk)) {238		pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk));239		return PTR_ERR(clk);240	}241	err = clk_prepare_enable(clk);242	if (err) {243		pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",244			cnt_name, err);245		return err;246	}247 248	clk = of_clk_get_by_name(np, ftm_name);249	if (IS_ERR(clk)) {250		pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk));251		return PTR_ERR(clk);252	}253	err = clk_prepare_enable(clk);254	if (err)255		pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",256			ftm_name, err);257 258	return clk_get_rate(clk);259}260 261static unsigned long __init ftm_clk_init(struct device_node *np)262{263	long freq;264 265	freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt");266	if (freq <= 0)267		return 0;268 269	freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src");270	if (freq <= 0)271		return 0;272 273	return freq;274}275 276static int __init ftm_calc_closest_round_cyc(unsigned long freq)277{278	priv->ps = 0;279 280	/* The counter register is only using the lower 16 bits, and281	 * if the 'freq' value is to big here, then the periodic_cyc282	 * may exceed 0xFFFF.283	 */284	do {285		priv->periodic_cyc = DIV_ROUND_CLOSEST(freq,286						HZ * (1 << priv->ps++));287	} while (priv->periodic_cyc > 0xFFFF);288 289	if (priv->ps > FTM_PS_MAX) {290		pr_err("ftm: the prescaler is %lu > %d\n",291				priv->ps, FTM_PS_MAX);292		return -EINVAL;293	}294 295	return 0;296}297 298static int __init ftm_timer_init(struct device_node *np)299{300	unsigned long freq;301	int ret, irq;302 303	priv = kzalloc(sizeof(*priv), GFP_KERNEL);304	if (!priv)305		return -ENOMEM;306 307	ret = -ENXIO;308	priv->clkevt_base = of_iomap(np, 0);309	if (!priv->clkevt_base) {310		pr_err("ftm: unable to map event timer registers\n");311		goto err_clkevt;312	}313 314	priv->clksrc_base = of_iomap(np, 1);315	if (!priv->clksrc_base) {316		pr_err("ftm: unable to map source timer registers\n");317		goto err_clksrc;318	}319 320	ret = -EINVAL;321	irq = irq_of_parse_and_map(np, 0);322	if (irq <= 0) {323		pr_err("ftm: unable to get IRQ from DT, %d\n", irq);324		goto err;325	}326 327	priv->big_endian = of_property_read_bool(np, "big-endian");328 329	freq = ftm_clk_init(np);330	if (!freq)331		goto err;332 333	ret = ftm_calc_closest_round_cyc(freq);334	if (ret)335		goto err;336 337	ret = ftm_clocksource_init(freq);338	if (ret)339		goto err;340 341	ret = ftm_clockevent_init(freq, irq);342	if (ret)343		goto err;344 345	return 0;346 347err:348	iounmap(priv->clksrc_base);349err_clksrc:350	iounmap(priv->clkevt_base);351err_clkevt:352	kfree(priv);353	return ret;354}355TIMER_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init);356