Path: blob/master/drivers/clocksource/timer-atmel-pit.c
26278 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* at91sam926x_time.c - Periodic Interval Timer (PIT) for at91sam926x3*4* Copyright (C) 2005-2006 M. Amine SAYA, ATMEL Rousset, France5* Revision 2005 M. Nicolas Diremdjian, ATMEL Rousset, France6* Converted to ClockSource/ClockEvents by David Brownell.7*/89#define pr_fmt(fmt) "AT91: PIT: " fmt1011#include <linux/clk.h>12#include <linux/clockchips.h>13#include <linux/interrupt.h>14#include <linux/irq.h>15#include <linux/kernel.h>16#include <linux/of.h>17#include <linux/of_address.h>18#include <linux/of_irq.h>19#include <linux/slab.h>2021#define AT91_PIT_MR 0x00 /* Mode Register */22#define AT91_PIT_PITIEN BIT(25) /* Timer Interrupt Enable */23#define AT91_PIT_PITEN BIT(24) /* Timer Enabled */24#define AT91_PIT_PIV GENMASK(19, 0) /* Periodic Interval Value */2526#define AT91_PIT_SR 0x04 /* Status Register */27#define AT91_PIT_PITS BIT(0) /* Timer Status */2829#define AT91_PIT_PIVR 0x08 /* Periodic Interval Value Register */30#define AT91_PIT_PIIR 0x0c /* Periodic Interval Image Register */31#define AT91_PIT_PICNT GENMASK(31, 20) /* Interval Counter */32#define AT91_PIT_CPIV GENMASK(19, 0) /* Inverval Value */3334#define PIT_CPIV(x) ((x) & AT91_PIT_CPIV)35#define PIT_PICNT(x) (((x) & AT91_PIT_PICNT) >> 20)3637struct pit_data {38struct clock_event_device clkevt;39struct clocksource clksrc;4041void __iomem *base;42u32 cycle;43u32 cnt;44unsigned int irq;45struct clk *mck;46};4748static inline struct pit_data *clksrc_to_pit_data(struct clocksource *clksrc)49{50return container_of(clksrc, struct pit_data, clksrc);51}5253static inline struct pit_data *clkevt_to_pit_data(struct clock_event_device *clkevt)54{55return container_of(clkevt, struct pit_data, clkevt);56}5758static inline unsigned int pit_read(void __iomem *base, unsigned int reg_offset)59{60return readl_relaxed(base + reg_offset);61}6263static inline void pit_write(void __iomem *base, unsigned int reg_offset, unsigned long value)64{65writel_relaxed(value, base + reg_offset);66}6768/*69* Clocksource: just a monotonic counter of MCK/16 cycles.70* We don't care whether or not PIT irqs are enabled.71*/72static u64 read_pit_clk(struct clocksource *cs)73{74struct pit_data *data = clksrc_to_pit_data(cs);75unsigned long flags;76u32 elapsed;77u32 t;7879raw_local_irq_save(flags);80elapsed = data->cnt;81t = pit_read(data->base, AT91_PIT_PIIR);82raw_local_irq_restore(flags);8384elapsed += PIT_PICNT(t) * data->cycle;85elapsed += PIT_CPIV(t);86return elapsed;87}8889static int pit_clkevt_shutdown(struct clock_event_device *dev)90{91struct pit_data *data = clkevt_to_pit_data(dev);9293/* disable irq, leaving the clocksource active */94pit_write(data->base, AT91_PIT_MR, (data->cycle - 1) | AT91_PIT_PITEN);95return 0;96}9798/*99* Clockevent device: interrupts every 1/HZ (== pit_cycles * MCK/16)100*/101static int pit_clkevt_set_periodic(struct clock_event_device *dev)102{103struct pit_data *data = clkevt_to_pit_data(dev);104105/* update clocksource counter */106data->cnt += data->cycle * PIT_PICNT(pit_read(data->base, AT91_PIT_PIVR));107pit_write(data->base, AT91_PIT_MR,108(data->cycle - 1) | AT91_PIT_PITEN | AT91_PIT_PITIEN);109return 0;110}111112static void at91sam926x_pit_suspend(struct clock_event_device *cedev)113{114struct pit_data *data = clkevt_to_pit_data(cedev);115116/* Disable timer */117pit_write(data->base, AT91_PIT_MR, 0);118}119120static void at91sam926x_pit_reset(struct pit_data *data)121{122/* Disable timer and irqs */123pit_write(data->base, AT91_PIT_MR, 0);124125/* Clear any pending interrupts, wait for PIT to stop counting */126while (PIT_CPIV(pit_read(data->base, AT91_PIT_PIVR)) != 0)127cpu_relax();128129/* Start PIT but don't enable IRQ */130pit_write(data->base, AT91_PIT_MR,131(data->cycle - 1) | AT91_PIT_PITEN);132}133134static void at91sam926x_pit_resume(struct clock_event_device *cedev)135{136struct pit_data *data = clkevt_to_pit_data(cedev);137138at91sam926x_pit_reset(data);139}140141/*142* IRQ handler for the timer.143*/144static irqreturn_t at91sam926x_pit_interrupt(int irq, void *dev_id)145{146struct pit_data *data = dev_id;147148/* The PIT interrupt may be disabled, and is shared */149if (clockevent_state_periodic(&data->clkevt) &&150(pit_read(data->base, AT91_PIT_SR) & AT91_PIT_PITS)) {151/* Get number of ticks performed before irq, and ack it */152data->cnt += data->cycle * PIT_PICNT(pit_read(data->base,153AT91_PIT_PIVR));154data->clkevt.event_handler(&data->clkevt);155156return IRQ_HANDLED;157}158159return IRQ_NONE;160}161162/*163* Set up both clocksource and clockevent support.164*/165static int __init at91sam926x_pit_dt_init(struct device_node *node)166{167unsigned long pit_rate;168unsigned bits;169int ret;170struct pit_data *data;171172data = kzalloc(sizeof(*data), GFP_KERNEL);173if (!data)174return -ENOMEM;175176data->base = of_iomap(node, 0);177if (!data->base) {178pr_err("Could not map PIT address\n");179ret = -ENXIO;180goto exit;181}182183data->mck = of_clk_get(node, 0);184if (IS_ERR(data->mck)) {185pr_err("Unable to get mck clk\n");186ret = PTR_ERR(data->mck);187goto exit;188}189190ret = clk_prepare_enable(data->mck);191if (ret) {192pr_err("Unable to enable mck\n");193goto exit;194}195196/* Get the interrupts property */197data->irq = irq_of_parse_and_map(node, 0);198if (!data->irq) {199pr_err("Unable to get IRQ from DT\n");200ret = -EINVAL;201goto exit;202}203204/*205* Use our actual MCK to figure out how many MCK/16 ticks per206* 1/HZ period (instead of a compile-time constant LATCH).207*/208pit_rate = clk_get_rate(data->mck) / 16;209data->cycle = DIV_ROUND_CLOSEST(pit_rate, HZ);210WARN_ON(((data->cycle - 1) & ~AT91_PIT_PIV) != 0);211212/* Initialize and enable the timer */213at91sam926x_pit_reset(data);214215/*216* Register clocksource. The high order bits of PIV are unused,217* so this isn't a 32-bit counter unless we get clockevent irqs.218*/219bits = 12 /* PICNT */ + ilog2(data->cycle) /* PIV */;220data->clksrc.mask = CLOCKSOURCE_MASK(bits);221data->clksrc.name = "pit";222data->clksrc.rating = 175;223data->clksrc.read = read_pit_clk;224data->clksrc.flags = CLOCK_SOURCE_IS_CONTINUOUS;225226ret = clocksource_register_hz(&data->clksrc, pit_rate);227if (ret) {228pr_err("Failed to register clocksource\n");229goto exit;230}231232/* Set up irq handler */233ret = request_irq(data->irq, at91sam926x_pit_interrupt,234IRQF_SHARED | IRQF_TIMER | IRQF_IRQPOLL,235"at91_tick", data);236if (ret) {237pr_err("Unable to setup IRQ\n");238clocksource_unregister(&data->clksrc);239goto exit;240}241242/* Set up and register clockevents */243data->clkevt.name = "pit";244data->clkevt.features = CLOCK_EVT_FEAT_PERIODIC;245data->clkevt.shift = 32;246data->clkevt.mult = div_sc(pit_rate, NSEC_PER_SEC, data->clkevt.shift);247data->clkevt.rating = 100;248data->clkevt.cpumask = cpumask_of(0);249250data->clkevt.set_state_shutdown = pit_clkevt_shutdown;251data->clkevt.set_state_periodic = pit_clkevt_set_periodic;252data->clkevt.resume = at91sam926x_pit_resume;253data->clkevt.suspend = at91sam926x_pit_suspend;254clockevents_register_device(&data->clkevt);255256return 0;257258exit:259kfree(data);260return ret;261}262TIMER_OF_DECLARE(at91sam926x_pit, "atmel,at91sam9260-pit",263at91sam926x_pit_dt_init);264265266