1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * linux/arch/m68k/hp300/time.c |
4 | * |
5 | * Copyright (C) 1998 Philip Blundell <philb@gnu.org> |
6 | * |
7 | * This file contains the HP300-specific time handling code. |
8 | */ |
9 | |
10 | #include <asm/ptrace.h> |
11 | #include <linux/clocksource.h> |
12 | #include <linux/types.h> |
13 | #include <linux/init.h> |
14 | #include <linux/sched.h> |
15 | #include <linux/kernel_stat.h> |
16 | #include <linux/interrupt.h> |
17 | #include <asm/machdep.h> |
18 | #include <asm/irq.h> |
19 | #include <asm/io.h> |
20 | #include <asm/traps.h> |
21 | #include <asm/blinken.h> |
22 | |
23 | #include "time.h" |
24 | |
25 | static u64 hp300_read_clk(struct clocksource *cs); |
26 | |
27 | static struct clocksource hp300_clk = { |
28 | .name = "timer" , |
29 | .rating = 250, |
30 | .read = hp300_read_clk, |
31 | .mask = CLOCKSOURCE_MASK(32), |
32 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, |
33 | }; |
34 | |
35 | static u32 clk_total, clk_offset; |
36 | |
37 | /* Clock hardware definitions */ |
38 | |
39 | #define CLOCKBASE 0xf05f8000 |
40 | |
41 | #define CLKCR1 0x1 |
42 | #define CLKCR2 0x3 |
43 | #define CLKCR3 CLKCR1 |
44 | #define CLKSR CLKCR2 |
45 | #define CLKMSB1 0x5 |
46 | #define CLKLSB1 0x7 |
47 | #define CLKMSB2 0x9 |
48 | #define CLKMSB3 0xD |
49 | |
50 | #define CLKSR_INT1 BIT(0) |
51 | |
52 | /* This is for machines which generate the exact clock. */ |
53 | |
54 | #define HP300_TIMER_CLOCK_FREQ 250000 |
55 | #define HP300_TIMER_CYCLES (HP300_TIMER_CLOCK_FREQ / HZ) |
56 | #define INTVAL (HP300_TIMER_CYCLES - 1) |
57 | |
58 | static irqreturn_t hp300_tick(int irq, void *dev_id) |
59 | { |
60 | unsigned long flags; |
61 | unsigned long tmp; |
62 | |
63 | local_irq_save(flags); |
64 | in_8(CLOCKBASE + CLKSR); |
65 | asm volatile ("movpw %1@(5),%0" : "=d" (tmp) : "a" (CLOCKBASE)); |
66 | clk_total += INTVAL; |
67 | clk_offset = 0; |
68 | legacy_timer_tick(ticks: 1); |
69 | timer_heartbeat(); |
70 | local_irq_restore(flags); |
71 | |
72 | /* Turn off the network and SCSI leds */ |
73 | blinken_leds(0, 0xe0); |
74 | return IRQ_HANDLED; |
75 | } |
76 | |
77 | static u64 hp300_read_clk(struct clocksource *cs) |
78 | { |
79 | unsigned long flags; |
80 | unsigned char lsb, msb, msb_new; |
81 | u32 ticks; |
82 | |
83 | local_irq_save(flags); |
84 | /* Read current timer 1 value */ |
85 | msb = in_8(CLOCKBASE + CLKMSB1); |
86 | again: |
87 | if ((in_8(CLOCKBASE + CLKSR) & CLKSR_INT1) && msb > 0) |
88 | clk_offset = INTVAL; |
89 | lsb = in_8(CLOCKBASE + CLKLSB1); |
90 | msb_new = in_8(CLOCKBASE + CLKMSB1); |
91 | if (msb_new != msb) { |
92 | msb = msb_new; |
93 | goto again; |
94 | } |
95 | |
96 | ticks = INTVAL - ((msb << 8) | lsb); |
97 | ticks += clk_offset + clk_total; |
98 | local_irq_restore(flags); |
99 | |
100 | return ticks; |
101 | } |
102 | |
103 | void __init hp300_sched_init(void) |
104 | { |
105 | out_8(CLOCKBASE + CLKCR2, 0x1); /* select CR1 */ |
106 | out_8(CLOCKBASE + CLKCR1, 0x1); /* reset */ |
107 | |
108 | asm volatile(" movpw %0,%1@(5)" : : "d" (INTVAL), "a" (CLOCKBASE)); |
109 | |
110 | if (request_irq(irq: IRQ_AUTO_6, handler: hp300_tick, IRQF_TIMER, name: "timer tick" , NULL)) |
111 | pr_err("Couldn't register timer interrupt\n" ); |
112 | |
113 | out_8(CLOCKBASE + CLKCR2, 0x1); /* select CR1 */ |
114 | out_8(CLOCKBASE + CLKCR1, 0x40); /* enable irq */ |
115 | |
116 | clocksource_register_hz(cs: &hp300_clk, HP300_TIMER_CLOCK_FREQ); |
117 | } |
118 | |