Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
Something went wrong. Try again.
6.2 kB · 273 lines
C
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274// 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);