273 lines · 6.0 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0-only/* * Copyright (C) 2015 ARM Limited * * Author: Vladimir Murzin <vladimir.murzin@arm.com> */ #define pr_fmt(fmt)	KBUILD_MODNAME ": " fmt #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/irq.h>#include <linux/of_address.h>#include <linux/of.h>#include <linux/of_irq.h>#include <linux/sched_clock.h>#include <linux/slab.h> #define TIMER_CTRL		0x0#define TIMER_CTRL_ENABLE	BIT(0)#define TIMER_CTRL_IE		BIT(3) #define TIMER_VALUE		0x4#define TIMER_RELOAD		0x8#define TIMER_INT		0xc struct clockevent_mps2 {	void __iomem *reg;	u32 clock_count_per_tick;	struct clock_event_device clkevt;}; static void __iomem *sched_clock_base; static u64 notrace mps2_sched_read(void){	return ~readl_relaxed(sched_clock_base + TIMER_VALUE);} static inline struct clockevent_mps2 *to_mps2_clkevt(struct clock_event_device *c){	return container_of(c, struct clockevent_mps2, clkevt);} static void clockevent_mps2_writel(u32 val, struct clock_event_device *c, u32 offset){	writel_relaxed(val, to_mps2_clkevt(c)->reg + offset);} static int mps2_timer_shutdown(struct clock_event_device *ce){	clockevent_mps2_writel(0, ce, TIMER_RELOAD);	clockevent_mps2_writel(0, ce, TIMER_CTRL); 	return 0;} static int mps2_timer_set_next_event(unsigned long next, struct clock_event_device *ce){	clockevent_mps2_writel(next, ce, TIMER_VALUE);	clockevent_mps2_writel(TIMER_CTRL_IE | TIMER_CTRL_ENABLE, ce, TIMER_CTRL); 	return 0;} static int mps2_timer_set_periodic(struct clock_event_device *ce){	u32 clock_count_per_tick = to_mps2_clkevt(ce)->clock_count_per_tick; 	clockevent_mps2_writel(clock_count_per_tick, ce, TIMER_RELOAD);	clockevent_mps2_writel(clock_count_per_tick, ce, TIMER_VALUE);	clockevent_mps2_writel(TIMER_CTRL_IE | TIMER_CTRL_ENABLE, ce, TIMER_CTRL); 	return 0;} static irqreturn_t mps2_timer_interrupt(int irq, void *dev_id){	struct clockevent_mps2 *ce = dev_id;	u32 status = readl_relaxed(ce->reg + TIMER_INT); 	if (!status) {		pr_warn("spurious interrupt\n");		return IRQ_NONE;	} 	writel_relaxed(1, ce->reg + TIMER_INT); 	ce->clkevt.event_handler(&ce->clkevt); 	return IRQ_HANDLED;} static int __init mps2_clockevent_init(struct device_node *np){	void __iomem *base;	struct clk *clk = NULL;	struct clockevent_mps2 *ce;	u32 rate;	int irq, ret;	const char *name = "mps2-clkevt"; 	ret = of_property_read_u32(np, "clock-frequency", &rate);	if (ret) {		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 out;		} 		ret = clk_prepare_enable(clk);		if (ret) {			pr_err("failed to enable clock for clockevent: %d\n", ret);			goto out_clk_put;		} 		rate = clk_get_rate(clk);	} 	base = of_iomap(np, 0);	if (!base) {		ret = -EADDRNOTAVAIL;		pr_err("failed to map register for clockevent: %d\n", ret);		goto out_clk_disable;	} 	irq = irq_of_parse_and_map(np, 0);	if (!irq) {		ret = -ENOENT;		pr_err("failed to get irq for clockevent: %d\n", ret);		goto out_iounmap;	} 	ce = kzalloc_obj(*ce);	if (!ce) {		ret = -ENOMEM;		goto out_iounmap;	} 	ce->reg = base;	ce->clock_count_per_tick = DIV_ROUND_CLOSEST(rate, HZ);	ce->clkevt.irq = irq;	ce->clkevt.name = name;	ce->clkevt.rating = 200;	ce->clkevt.features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT;	ce->clkevt.cpumask = cpu_possible_mask;	ce->clkevt.set_state_shutdown	= mps2_timer_shutdown;	ce->clkevt.set_state_periodic	= mps2_timer_set_periodic;	ce->clkevt.set_state_oneshot	= mps2_timer_shutdown;	ce->clkevt.set_next_event	= mps2_timer_set_next_event; 	/* Ensure timer is disabled */	writel_relaxed(0, base + TIMER_CTRL); 	ret = request_irq(irq, mps2_timer_interrupt, IRQF_TIMER, name, ce);	if (ret) {		pr_err("failed to request irq for clockevent: %d\n", ret);		goto out_kfree;	} 	clockevents_config_and_register(&ce->clkevt, rate, 0xf, 0xffffffff); 	return 0; out_kfree:	kfree(ce);out_iounmap:	iounmap(base);out_clk_disable:	/* clk_{disable, unprepare, put}() can handle NULL as a parameter */	clk_disable_unprepare(clk);out_clk_put:	clk_put(clk);out:	return ret;} static int __init mps2_clocksource_init(struct device_node *np){	void __iomem *base;	struct clk *clk = NULL;	u32 rate;	int ret;	const char *name = "mps2-clksrc"; 	ret = of_property_read_u32(np, "clock-frequency", &rate);	if (ret) {		clk = of_clk_get(np, 0);		if (IS_ERR(clk)) {			ret = PTR_ERR(clk);			pr_err("failed to get clock for clocksource: %d\n", ret);			goto out;		} 		ret = clk_prepare_enable(clk);		if (ret) {			pr_err("failed to enable clock for clocksource: %d\n", ret);			goto out_clk_put;		} 		rate = clk_get_rate(clk);	} 	base = of_iomap(np, 0);	if (!base) {		ret = -EADDRNOTAVAIL;		pr_err("failed to map register for clocksource: %d\n", ret);		goto out_clk_disable;	} 	/* Ensure timer is disabled */	writel_relaxed(0, base + TIMER_CTRL); 	/* ... and set it up as free-running clocksource */	writel_relaxed(0xffffffff, base + TIMER_VALUE);	writel_relaxed(0xffffffff, base + TIMER_RELOAD); 	writel_relaxed(TIMER_CTRL_ENABLE, base + TIMER_CTRL); 	ret = clocksource_mmio_init(base + TIMER_VALUE, name,				    rate, 200, 32,				    clocksource_mmio_readl_down);	if (ret) {		pr_err("failed to init clocksource: %d\n", ret);		goto out_iounmap;	} 	sched_clock_base = base;	sched_clock_register(mps2_sched_read, 32, rate); 	return 0; out_iounmap:	iounmap(base);out_clk_disable:	/* clk_{disable, unprepare, put}() can handle NULL as a parameter */	clk_disable_unprepare(clk);out_clk_put:	clk_put(clk);out:	return ret;} static int __init mps2_timer_init(struct device_node *np){	static int has_clocksource, has_clockevent;	int ret; 	if (!has_clocksource) {		ret = mps2_clocksource_init(np);		if (!ret) {			has_clocksource = 1;			return 0;		}	} 	if (!has_clockevent) {		ret = mps2_clockevent_init(np);		if (!ret) {			has_clockevent = 1;			return 0;		}	} 	return 0;} TIMER_OF_DECLARE(mps2_timer, "arm,mps2-timer", mps2_timer_init);