| 1 | |
| 2 | |
| 3 | * at91sam926x_time.c - Periodic Interval Timer (PIT) for at91sam926x
|
| 4 | *
|
| 5 | * Copyright (C) 2005-2006 M. Amine SAYA, ATMEL Rousset, France
|
| 6 | * Revision 2005 M. Nicolas Diremdjian, ATMEL Rousset, France
|
| 7 | * Converted to ClockSource/ClockEvents by David Brownell.
|
| 8 |
|
| 9 |
|
| 10 | #define pr_fmt(fmt) "AT91: PIT: " fmt
|
| 11 |
|
| 12 | #include <linux/clk.h>
|
| 13 | #include <linux/clockchips.h>
|
| 14 | #include <linux/interrupt.h>
|
| 15 | #include <linux/irq.h>
|
| 16 | #include <linux/kernel.h>
|
| 17 | #include <linux/of.h>
|
| 18 | #include <linux/of_address.h>
|
| 19 | #include <linux/of_irq.h>
|
| 20 | #include <linux/slab.h>
|
| 21 |
|
| 22 | #define AT91_PIT_MR 0x00
|
| 23 | #define AT91_PIT_PITIEN BIT(25)
|
| 24 | #define AT91_PIT_PITEN BIT(24)
|
| 25 | #define AT91_PIT_PIV GENMASK(19, 0)
|
| 26 |
|
| 27 | #define AT91_PIT_SR 0x04
|
| 28 | #define AT91_PIT_PITS BIT(0)
|
| 29 |
|
| 30 | #define AT91_PIT_PIVR 0x08
|
| 31 | #define AT91_PIT_PIIR 0x0c
|
| 32 | #define AT91_PIT_PICNT GENMASK(31, 20)
|
| 33 | #define AT91_PIT_CPIV GENMASK(19, 0)
|
| 34 |
|
| 35 | #define PIT_CPIV(x) ((x) & AT91_PIT_CPIV)
|
| 36 | #define PIT_PICNT(x) (((x) & AT91_PIT_PICNT) >> 20)
|
| 37 |
|
| 38 | struct pit_data {
|
| 39 | struct clock_event_device clkevt;
|
| 40 | struct clocksource clksrc;
|
| 41 |
|
| 42 | void __iomem *base;
|
| 43 | u32 cycle;
|
| 44 | u32 cnt;
|
| 45 | unsigned int irq;
|
| 46 | struct clk *mck;
|
| 47 | };
|
| 48 |
|
| 49 | static inline struct pit_data *clksrc_to_pit_data(struct clocksource *clksrc)
|
| 50 | {
|
| 51 | return container_of(clksrc, struct pit_data, clksrc);
|
| 52 | }
|
| 53 |
|
| 54 | static inline struct pit_data *clkevt_to_pit_data(struct clock_event_device *clkevt)
|
| 55 | {
|
| 56 | return container_of(clkevt, struct pit_data, clkevt);
|
| 57 | }
|
| 58 |
|
| 59 | static inline unsigned int pit_read(void __iomem *base, unsigned int reg_offset)
|
| 60 | {
|
| 61 | return readl_relaxed(base + reg_offset);
|
| 62 | }
|
| 63 |
|
| 64 | static inline void pit_write(void __iomem *base, unsigned int reg_offset, unsigned long value)
|
| 65 | {
|
| 66 | writel_relaxed(value, base + reg_offset);
|
| 67 | }
|
| 68 |
|
| 69 | |
| 70 | * Clocksource: just a monotonic counter of MCK/16 cycles.
|
| 71 | * We don't care whether or not PIT irqs are enabled.
|
| 72 |
|
| 73 | static u64 read_pit_clk(struct clocksource *cs)
|
| 74 | {
|
| 75 | struct pit_data *data = clksrc_to_pit_data(cs);
|
| 76 | unsigned long flags;
|
| 77 | u32 elapsed;
|
| 78 | u32 t;
|
| 79 |
|
| 80 | raw_local_irq_save(flags);
|
| 81 | elapsed = data->cnt;
|
| 82 | t = pit_read(data->base, AT91_PIT_PIIR);
|
| 83 | raw_local_irq_restore(flags);
|
| 84 |
|
| 85 | elapsed += PIT_PICNT(t) * data->cycle;
|
| 86 | elapsed += PIT_CPIV(t);
|
| 87 | return elapsed;
|
| 88 | }
|
| 89 |
|
| 90 | static int pit_clkevt_shutdown(struct clock_event_device *dev)
|
| 91 | {
|
| 92 | struct pit_data *data = clkevt_to_pit_data(dev);
|
| 93 |
|
| 94 |
|
| 95 | pit_write(data->base, AT91_PIT_MR, (data->cycle - 1) | AT91_PIT_PITEN);
|
| 96 | return 0;
|
| 97 | }
|
| 98 |
|
| 99 | |
| 100 | * Clockevent device: interrupts every 1/HZ (== pit_cycles * MCK/16)
|
| 101 |
|
| 102 | static int pit_clkevt_set_periodic(struct clock_event_device *dev)
|
| 103 | {
|
| 104 | struct pit_data *data = clkevt_to_pit_data(dev);
|
| 105 |
|
| 106 |
|
| 107 | data->cnt += data->cycle * PIT_PICNT(pit_read(data->base, AT91_PIT_PIVR));
|
| 108 | pit_write(data->base, AT91_PIT_MR,
|
| 109 | (data->cycle - 1) | AT91_PIT_PITEN | AT91_PIT_PITIEN);
|
| 110 | return 0;
|
| 111 | }
|
| 112 |
|
| 113 | static void at91sam926x_pit_suspend(struct clock_event_device *cedev)
|
| 114 | {
|
| 115 | struct pit_data *data = clkevt_to_pit_data(cedev);
|
| 116 |
|
| 117 |
|
| 118 | pit_write(data->base, AT91_PIT_MR, 0);
|
| 119 | }
|
| 120 |
|
| 121 | static void at91sam926x_pit_reset(struct pit_data *data)
|
| 122 | {
|
| 123 |
|
| 124 | pit_write(data->base, AT91_PIT_MR, 0);
|
| 125 |
|
| 126 |
|
| 127 | while (PIT_CPIV(pit_read(data->base, AT91_PIT_PIVR)) != 0)
|
| 128 | cpu_relax();
|
| 129 |
|
| 130 |
|
| 131 | pit_write(data->base, AT91_PIT_MR,
|
| 132 | (data->cycle - 1) | AT91_PIT_PITEN);
|
| 133 | }
|
| 134 |
|
| 135 | static void at91sam926x_pit_resume(struct clock_event_device *cedev)
|
| 136 | {
|
| 137 | struct pit_data *data = clkevt_to_pit_data(cedev);
|
| 138 |
|
| 139 | at91sam926x_pit_reset(data);
|
| 140 | }
|
| 141 |
|
| 142 | |
| 143 | * IRQ handler for the timer.
|
| 144 |
|
| 145 | static irqreturn_t at91sam926x_pit_interrupt(int irq, void *dev_id)
|
| 146 | {
|
| 147 | struct pit_data *data = dev_id;
|
| 148 |
|
| 149 |
|
| 150 | if (clockevent_state_periodic(&data->clkevt) &&
|
| 151 | (pit_read(data->base, AT91_PIT_SR) & AT91_PIT_PITS)) {
|
| 152 |
|
| 153 | data->cnt += data->cycle * PIT_PICNT(pit_read(data->base,
|
| 154 | AT91_PIT_PIVR));
|
| 155 | data->clkevt.event_handler(&data->clkevt);
|
| 156 |
|
| 157 | return IRQ_HANDLED;
|
| 158 | }
|
| 159 |
|
| 160 | return IRQ_NONE;
|
| 161 | }
|
| 162 |
|
| 163 | |
| 164 | * Set up both clocksource and clockevent support.
|
| 165 |
|
| 166 | static int __init at91sam926x_pit_dt_init(struct device_node *node)
|
| 167 | {
|
| 168 | unsigned long pit_rate;
|
| 169 | unsigned bits;
|
| 170 | int ret;
|
| 171 | struct pit_data *data;
|
| 172 |
|
| 173 | data = kzalloc_obj(*data);
|
| 174 | if (!data)
|
| 175 | return -ENOMEM;
|
| 176 |
|
| 177 | data->base = of_iomap(node, 0);
|
| 178 | if (!data->base) {
|
| 179 | pr_err("Could not map PIT address\n");
|
| 180 | ret = -ENXIO;
|
| 181 | goto exit;
|
| 182 | }
|
| 183 |
|
| 184 | data->mck = of_clk_get(node, 0);
|
| 185 | if (IS_ERR(data->mck)) {
|
| 186 | pr_err("Unable to get mck clk\n");
|
| 187 | ret = PTR_ERR(data->mck);
|
| 188 | goto exit;
|
| 189 | }
|
| 190 |
|
| 191 | ret = clk_prepare_enable(data->mck);
|
| 192 | if (ret) {
|
| 193 | pr_err("Unable to enable mck\n");
|
| 194 | goto exit;
|
| 195 | }
|
| 196 |
|
| 197 |
|
| 198 | data->irq = irq_of_parse_and_map(node, 0);
|
| 199 | if (!data->irq) {
|
| 200 | pr_err("Unable to get IRQ from DT\n");
|
| 201 | ret = -EINVAL;
|
| 202 | goto exit;
|
| 203 | }
|
| 204 |
|
| 205 | |
| 206 | * Use our actual MCK to figure out how many MCK/16 ticks per
|
| 207 | * 1/HZ period (instead of a compile-time constant LATCH).
|
| 208 |
|
| 209 | pit_rate = clk_get_rate(data->mck) / 16;
|
| 210 | data->cycle = DIV_ROUND_CLOSEST(pit_rate, HZ);
|
| 211 | WARN_ON(((data->cycle - 1) & ~AT91_PIT_PIV) != 0);
|
| 212 |
|
| 213 |
|
| 214 | at91sam926x_pit_reset(data);
|
| 215 |
|
| 216 | |
| 217 | * Register clocksource. The high order bits of PIV are unused,
|
| 218 | * so this isn't a 32-bit counter unless we get clockevent irqs.
|
| 219 |
|
| 220 | bits = 12 + ilog2(data->cycle) ;
|
| 221 | data->clksrc.mask = CLOCKSOURCE_MASK(bits);
|
| 222 | data->clksrc.name = "pit";
|
| 223 | data->clksrc.rating = 175;
|
| 224 | data->clksrc.read = read_pit_clk;
|
| 225 | data->clksrc.flags = CLOCK_SOURCE_IS_CONTINUOUS;
|
| 226 |
|
| 227 | ret = clocksource_register_hz(&data->clksrc, pit_rate);
|
| 228 | if (ret) {
|
| 229 | pr_err("Failed to register clocksource\n");
|
| 230 | goto exit;
|
| 231 | }
|
| 232 |
|
| 233 |
|
| 234 | ret = request_irq(data->irq, at91sam926x_pit_interrupt,
|
| 235 | IRQF_SHARED | IRQF_TIMER | IRQF_IRQPOLL,
|
| 236 | "at91_tick", data);
|
| 237 | if (ret) {
|
| 238 | pr_err("Unable to setup IRQ\n");
|
| 239 | clocksource_unregister(&data->clksrc);
|
| 240 | goto exit;
|
| 241 | }
|
| 242 |
|
| 243 |
|
| 244 | data->clkevt.name = "pit";
|
| 245 | data->clkevt.features = CLOCK_EVT_FEAT_PERIODIC;
|
| 246 | data->clkevt.shift = 32;
|
| 247 | data->clkevt.mult = div_sc(pit_rate, NSEC_PER_SEC, data->clkevt.shift);
|
| 248 | data->clkevt.rating = 100;
|
| 249 | data->clkevt.cpumask = cpumask_of(0);
|
| 250 |
|
| 251 | data->clkevt.set_state_shutdown = pit_clkevt_shutdown;
|
| 252 | data->clkevt.set_state_periodic = pit_clkevt_set_periodic;
|
| 253 | data->clkevt.resume = at91sam926x_pit_resume;
|
| 254 | data->clkevt.suspend = at91sam926x_pit_suspend;
|
| 255 | clockevents_register_device(&data->clkevt);
|
| 256 |
|
| 257 | return 0;
|
| 258 |
|
| 259 | exit:
|
| 260 | kfree(data);
|
| 261 | return ret;
|
| 262 | }
|
| 263 | TIMER_OF_DECLARE(at91sam926x_pit, "atmel,at91sam9260-pit",
|
| 264 | at91sam926x_pit_dt_init);
|