xref: /netbsd-src/sys/arch/arm/footbridge/footbridge_clock.c (revision 77e75c280a6ef9de02ed9c7c1ed432312348a840)
1*77e75c28Sskrll /*	$NetBSD: footbridge_clock.c,v 1.27 2021/08/13 11:40:43 skrll Exp $	*/
2af8ce959Schris 
3af8ce959Schris /*
4af8ce959Schris  * Copyright (c) 1997 Mark Brinicombe.
5af8ce959Schris  * Copyright (c) 1997 Causality Limited.
6af8ce959Schris  * All rights reserved.
7af8ce959Schris  *
8af8ce959Schris  * Redistribution and use in source and binary forms, with or without
9af8ce959Schris  * modification, are permitted provided that the following conditions
10af8ce959Schris  * are met:
11af8ce959Schris  * 1. Redistributions of source code must retain the above copyright
12af8ce959Schris  *    notice, this list of conditions and the following disclaimer.
13af8ce959Schris  * 2. Redistributions in binary form must reproduce the above copyright
14af8ce959Schris  *    notice, this list of conditions and the following disclaimer in the
15af8ce959Schris  *    documentation and/or other materials provided with the distribution.
16af8ce959Schris  * 3. All advertising materials mentioning features or use of this software
17af8ce959Schris  *    must display the following acknowledgement:
18af8ce959Schris  *	This product includes software developed by Mark Brinicombe
19af8ce959Schris  *	for the NetBSD Project.
20af8ce959Schris  * 4. The name of the company nor the name of the author may be used to
21af8ce959Schris  *    endorse or promote products derived from this software without specific
22af8ce959Schris  *    prior written permission.
23af8ce959Schris  *
24af8ce959Schris  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
25af8ce959Schris  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26af8ce959Schris  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
27af8ce959Schris  * IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
28af8ce959Schris  * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
29af8ce959Schris  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
30af8ce959Schris  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31af8ce959Schris  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32af8ce959Schris  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33af8ce959Schris  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34af8ce959Schris  * SUCH DAMAGE.
35af8ce959Schris  */
36af8ce959Schris 
379fd86b68Schris #include <sys/cdefs.h>
38*77e75c28Sskrll __KERNEL_RCSID(0, "$NetBSD: footbridge_clock.c,v 1.27 2021/08/13 11:40:43 skrll Exp $");
399fd86b68Schris 
40af8ce959Schris /* Include header files */
41af8ce959Schris 
42af8ce959Schris #include <sys/types.h>
43af8ce959Schris #include <sys/param.h>
44af8ce959Schris #include <sys/systm.h>
45af8ce959Schris #include <sys/kernel.h>
46af8ce959Schris #include <sys/time.h>
476cfcc60eSgdamore #include <sys/timetc.h>
48af8ce959Schris #include <sys/device.h>
49af8ce959Schris 
50792b7ebdSmatt #include <machine/intr.h>
510c57d872Sthorpej 
520c57d872Sthorpej #include <arm/cpufunc.h>
530c57d872Sthorpej 
54af8ce959Schris #include <arm/footbridge/dc21285reg.h>
55af8ce959Schris #include <arm/footbridge/footbridgevar.h>
566c4ac1deSchris #include <arm/footbridge/footbridge.h>
57af8ce959Schris 
58af8ce959Schris extern struct footbridge_softc *clock_sc;
59af8ce959Schris extern u_int dc21285_fclk;
60af8ce959Schris 
61a78ddf8bSgdamore int clockhandler(void *);
62a78ddf8bSgdamore int statclockhandler(void *);
63a78ddf8bSgdamore static int load_timer(int, int);
64e3a3a9f5Schris 
65dfcb3e35Schris /*
66dfcb3e35Schris  * Statistics clock variance, in usec.  Variance must be a
67dfcb3e35Schris  * power of two.  Since this gives us an even number, not an odd number,
68dfcb3e35Schris  * we discard one case and compensate.  That is, a variance of 1024 would
69dfcb3e35Schris  * give us offsets in [0..1023].  Instead, we take offsets in [1..1023].
70dfcb3e35Schris  * This is symmetric about the point 512, or statvar/2, and thus averages
71dfcb3e35Schris  * to that value (assuming uniform random numbers).
72dfcb3e35Schris  */
73dfcb3e35Schris const int statvar = 1024;
74dfcb3e35Schris int statmin;			/* minimum stat clock count in ticks */
75dfcb3e35Schris int statcountperusec;		/* number of ticks per usec at current stathz */
76dfcb3e35Schris int statprev;			/* last value of we set statclock to */
77e3a3a9f5Schris 
786cfcc60eSgdamore void footbridge_tc_init(void);
796cfcc60eSgdamore 
80af8ce959Schris #if 0
819cbe4c86Sskrll static int clockmatch(device_t parent, cfdata_t cf, void *aux);
829cbe4c86Sskrll static void clockattach(device_t parent, device_t self, void *aux);
83af8ce959Schris 
849cbe4c86Sskrll CFATTACH_DECL_NEW(footbridge_clock, sizeof(struct clock_softc),
85c5e91d44Sthorpej     clockmatch, clockattach, NULL, NULL);
86af8ce959Schris 
87af8ce959Schris /*
889cbe4c86Sskrll  * int clockmatch(device_t parent, cfdata_t cf, void *aux);
89af8ce959Schris  *
90af8ce959Schris  * Just return ok for this if it is device 0
91af8ce959Schris  */
92af8ce959Schris 
93af8ce959Schris static int
949cbe4c86Sskrll clockmatch(device_t parent, cfdata_t cf, void *aux)
95af8ce959Schris {
96af8ce959Schris 	union footbridge_attach_args *fba = aux;
97af8ce959Schris 
98af8ce959Schris 	if (strcmp(fba->fba_ca.ca_name, "clk") == 0)
99a78ddf8bSgdamore 		return 1;
100a78ddf8bSgdamore 	return 0;
101af8ce959Schris }
102af8ce959Schris 
103af8ce959Schris 
104af8ce959Schris /*
1059cbe4c86Sskrll  * void clockattach(device_t parent, device_t self, void *aux)
106af8ce959Schris  *
107af8ce959Schris  */
108af8ce959Schris 
109af8ce959Schris static void
1109cbe4c86Sskrll clockattach(device_t parent, device_t self, void *aux)
111af8ce959Schris {
1129cbe4c86Sskrll 	struct clock_softc *sc = device_private(self);
113af8ce959Schris 	union footbridge_attach_args *fba = aux;
114af8ce959Schris 
1159cbe4c86Sskrll 	sc->sc_dev = self;
116af8ce959Schris 	sc->sc_iot = fba->fba_ca.ca_iot;
117af8ce959Schris 	sc->sc_ioh = fba->fba_ca.ca_ioh;
118af8ce959Schris 
119af8ce959Schris 	clock_sc = sc;
120af8ce959Schris 
121af8ce959Schris 	/* Cannot do anything until cpu_initclocks() has been called */
122af8ce959Schris 
1239cbe4c86Sskrll 	aprint_normal("\n");
124af8ce959Schris }
125af8ce959Schris #endif
126af8ce959Schris 
127af8ce959Schris /*
128af8ce959Schris  * int clockhandler(struct clockframe *frame)
129af8ce959Schris  *
130af8ce959Schris  * Function called by timer 1 interrupts.
131af8ce959Schris  * This just clears the interrupt condition and calls hardclock().
132af8ce959Schris  */
133af8ce959Schris 
134af8ce959Schris int
clockhandler(void * aframe)135a78ddf8bSgdamore clockhandler(void *aframe)
136af8ce959Schris {
137e3a3a9f5Schris 	struct clockframe *frame = aframe;
138af8ce959Schris 	bus_space_write_4(clock_sc->sc_iot, clock_sc->sc_ioh,
139af8ce959Schris 	    TIMER_1_CLEAR, 0);
140af8ce959Schris 	hardclock(frame);
141a78ddf8bSgdamore 	return 0;	/* Pass the interrupt on down the chain */
142af8ce959Schris }
143af8ce959Schris 
144af8ce959Schris /*
145af8ce959Schris  * int statclockhandler(struct clockframe *frame)
146af8ce959Schris  *
147af8ce959Schris  * Function called by timer 2 interrupts.
148af8ce959Schris  * This just clears the interrupt condition and calls statclock().
149af8ce959Schris  */
150af8ce959Schris 
151af8ce959Schris int
statclockhandler(void * aframe)152a78ddf8bSgdamore statclockhandler(void *aframe)
153af8ce959Schris {
154e3a3a9f5Schris 	struct clockframe *frame = aframe;
155dfcb3e35Schris 	int newint, r;
156dfcb3e35Schris 	int currentclock ;
157dfcb3e35Schris 
158dfcb3e35Schris 	/* start the clock off again */
159af8ce959Schris 	bus_space_write_4(clock_sc->sc_iot, clock_sc->sc_ioh,
160af8ce959Schris 			TIMER_2_CLEAR, 0);
161dfcb3e35Schris 
162dfcb3e35Schris 	do {
163dfcb3e35Schris 		r = random() & (statvar-1);
164dfcb3e35Schris 	} while (r == 0);
165dfcb3e35Schris 	newint = statmin + (r * statcountperusec);
166dfcb3e35Schris 
167dfcb3e35Schris 	/* fetch the current count */
168dfcb3e35Schris 	currentclock = bus_space_read_4(clock_sc->sc_iot, clock_sc->sc_ioh,
169dfcb3e35Schris 		    TIMER_2_VALUE);
170dfcb3e35Schris 
171dfcb3e35Schris 	/*
172dfcb3e35Schris 	 * work out how much time has run, add another usec for time spent
173dfcb3e35Schris 	 * here
174dfcb3e35Schris 	 */
175dfcb3e35Schris 	r = ((statprev - currentclock) + statcountperusec);
176dfcb3e35Schris 
177dfcb3e35Schris 	if (r < newint) {
178dfcb3e35Schris 		newint -= r;
179dfcb3e35Schris 		r = 0;
180dfcb3e35Schris 	}
181dfcb3e35Schris 	else
182dfcb3e35Schris 		printf("statclockhandler: Statclock overrun\n");
183dfcb3e35Schris 
184dfcb3e35Schris 
185dfcb3e35Schris 	/*
186dfcb3e35Schris 	 * update the clock to the new counter, this reloads the existing
187dfcb3e35Schris 	 * timer
188dfcb3e35Schris 	 */
189dfcb3e35Schris 	bus_space_write_4(clock_sc->sc_iot, clock_sc->sc_ioh,
190dfcb3e35Schris 	    		TIMER_2_LOAD, newint);
191dfcb3e35Schris 	statprev = newint;
192af8ce959Schris 	statclock(frame);
193dfcb3e35Schris 	if (r)
194dfcb3e35Schris 		/*
195dfcb3e35Schris 		 * We've completely overrun the previous interval,
196dfcb3e35Schris 		 * make sure we report the correct number of ticks.
197dfcb3e35Schris 		 */
198dfcb3e35Schris 		statclock(frame);
199dfcb3e35Schris 
200a78ddf8bSgdamore 	return 0;	/* Pass the interrupt on down the chain */
201af8ce959Schris }
202af8ce959Schris 
203af8ce959Schris static int
load_timer(int base,int herz)204a78ddf8bSgdamore load_timer(int base, int herz)
205af8ce959Schris {
206af8ce959Schris 	unsigned int timer_count;
207af8ce959Schris 	int control;
208af8ce959Schris 
209172a6238She 	timer_count = dc21285_fclk / herz;
21023bc2503Sthorpej 	if (timer_count > TIMER_MAX_VAL * 16) {
211af8ce959Schris 		control = TIMER_FCLK_256;
212af8ce959Schris 		timer_count >>= 8;
21323bc2503Sthorpej 	} else if (timer_count > TIMER_MAX_VAL) {
214af8ce959Schris 		control = TIMER_FCLK_16;
215af8ce959Schris 		timer_count >>= 4;
216af8ce959Schris 	} else
217af8ce959Schris 		control = TIMER_FCLK;
218af8ce959Schris 
219af8ce959Schris 	control |= (TIMER_ENABLE | TIMER_MODE_PERIODIC);
220af8ce959Schris 	bus_space_write_4(clock_sc->sc_iot, clock_sc->sc_ioh,
221af8ce959Schris 	    base + TIMER_LOAD, timer_count);
222af8ce959Schris 	bus_space_write_4(clock_sc->sc_iot, clock_sc->sc_ioh,
223af8ce959Schris 	    base + TIMER_CONTROL, control);
224af8ce959Schris 	bus_space_write_4(clock_sc->sc_iot, clock_sc->sc_ioh,
225af8ce959Schris 	    base + TIMER_CLEAR, 0);
226a78ddf8bSgdamore 	return timer_count;
227af8ce959Schris }
228af8ce959Schris 
229af8ce959Schris /*
230172a6238She  * void setstatclockrate(int herz)
231af8ce959Schris  *
232af8ce959Schris  * Set the stat clock rate. The stat clock uses timer2
233af8ce959Schris  */
234af8ce959Schris 
235af8ce959Schris void
setstatclockrate(int herz)236a78ddf8bSgdamore setstatclockrate(int herz)
237af8ce959Schris {
238dfcb3e35Schris 	int statint;
239dfcb3e35Schris 	int countpersecond;
240dfcb3e35Schris 	int statvarticks;
241af8ce959Schris 
242172a6238She 	/* statint == num in counter to drop by desired herz */
2434ece245bStsutsui 	statint = statprev = clock_sc->sc_statclock_count =
244172a6238She 	    load_timer(TIMER_2_BASE, herz);
245dfcb3e35Schris 
246dfcb3e35Schris 	/* Get the total ticks a second */
247172a6238She 	countpersecond = statint * herz;
248dfcb3e35Schris 
249dfcb3e35Schris 	/* now work out how many ticks per usec */
250dfcb3e35Schris 	statcountperusec = countpersecond / 1000000;
251dfcb3e35Schris 
252dfcb3e35Schris 	/* calculate a variance range of statvar */
253dfcb3e35Schris 	statvarticks = statcountperusec * statvar;
254dfcb3e35Schris 
255dfcb3e35Schris 	/* minimum is statint - 50% of variant */
256dfcb3e35Schris 	statmin = statint - (statvarticks / 2);
257af8ce959Schris }
258af8ce959Schris 
259af8ce959Schris /*
260af8ce959Schris  * void cpu_initclocks(void)
261af8ce959Schris  *
262af8ce959Schris  * Initialise the clocks.
263af8ce959Schris  *
264af8ce959Schris  * Timer 1 is used for the main system clock (hardclock)
265af8ce959Schris  * Timer 2 is used for the statistics clock (statclock)
266af8ce959Schris  */
267af8ce959Schris 
268af8ce959Schris void
cpu_initclocks(void)269a78ddf8bSgdamore cpu_initclocks(void)
270af8ce959Schris {
2714c558675Schris 	/* stathz and profhz should be set to something, we have the timer */
2724c558675Schris 	if (stathz == 0)
273dfcb3e35Schris 		stathz = hz;
2744c558675Schris 
2754c558675Schris 	if (profhz == 0)
2764c558675Schris 		profhz = stathz * 5;
277af8ce959Schris 
278af8ce959Schris 	/* Report the clock frequencies */
2799cbe4c86Sskrll 	aprint_debug("clock: hz=%d stathz = %d profhz = %d\n", hz, stathz, profhz);
280af8ce959Schris 
281af8ce959Schris 	/* Setup timer 1 and claim interrupt */
282af8ce959Schris 	clock_sc->sc_clock_count = load_timer(TIMER_1_BASE, hz);
283af8ce959Schris 
284af8ce959Schris 	/*
285af8ce959Schris 	 * Use ticks per 256us for accuracy since ticks per us is often
286af8ce959Schris 	 * fractional e.g. @ 66MHz
287af8ce959Schris 	 */
288af8ce959Schris 	clock_sc->sc_clock_ticks_per_256us =
289af8ce959Schris 	    ((((clock_sc->sc_clock_count * hz) / 1000) * 256) / 1000);
29061578bc3Schris 	clock_sc->sc_clockintr = footbridge_intr_claim(IRQ_TIMER_1, IPL_CLOCK,
291af8ce959Schris 	    "tmr1 hard clk", clockhandler, 0);
292af8ce959Schris 
293af8ce959Schris 	if (clock_sc->sc_clockintr == NULL)
2940f09ed48Sprovos 		panic("%s: Cannot install timer 1 interrupt handler",
2959cbe4c86Sskrll 		    device_xname(clock_sc->sc_dev));
296af8ce959Schris 
297af8ce959Schris 	/* If stathz is non-zero then setup the stat clock */
298af8ce959Schris 	if (stathz) {
299af8ce959Schris 		/* Setup timer 2 and claim interrupt */
300af8ce959Schris 		setstatclockrate(stathz);
3014b293a84Sad        		clock_sc->sc_statclockintr = footbridge_intr_claim(IRQ_TIMER_2, IPL_HIGH,
302af8ce959Schris        		    "tmr2 stat clk", statclockhandler, 0);
303af8ce959Schris 		if (clock_sc->sc_statclockintr == NULL)
3040f09ed48Sprovos 			panic("%s: Cannot install timer 2 interrupt handler",
3059cbe4c86Sskrll 			    device_xname(clock_sc->sc_dev));
306af8ce959Schris 	}
3076cfcc60eSgdamore 
3086cfcc60eSgdamore 	footbridge_tc_init();
309af8ce959Schris }
310af8ce959Schris 
3116cfcc60eSgdamore static uint32_t
fclk_get_count(struct timecounter * tc)3126cfcc60eSgdamore fclk_get_count(struct timecounter *tc)
3136cfcc60eSgdamore {
3146cfcc60eSgdamore 	return (TIMER_MAX_VAL -
3156cfcc60eSgdamore 	    bus_space_read_4(clock_sc->sc_iot, clock_sc->sc_ioh,
3166cfcc60eSgdamore 	    TIMER_3_VALUE));
3176cfcc60eSgdamore }
318af8ce959Schris 
319af8ce959Schris void
footbridge_tc_init(void)3206cfcc60eSgdamore footbridge_tc_init(void)
321af8ce959Schris {
3226cfcc60eSgdamore 	static struct timecounter fb_tc = {
3236cfcc60eSgdamore 		.tc_get_timecount = fclk_get_count,
3246cfcc60eSgdamore 		.tc_counter_mask = TIMER_MAX_VAL,
3256cfcc60eSgdamore 		.tc_name = "dc21285_fclk",
3266cfcc60eSgdamore 		.tc_quality = 100
3276cfcc60eSgdamore 	};
3286cfcc60eSgdamore 	fb_tc.tc_frequency = dc21285_fclk;
3296cfcc60eSgdamore 	tc_init(&fb_tc);
330af8ce959Schris }
331af8ce959Schris 
332af8ce959Schris /*
3336c4ac1deSchris  * Use a timer to track microseconds, if the footbridge hasn't been setup we
3346c4ac1deSchris  * rely on an estimated loop, however footbridge is attached very early on.
335af8ce959Schris  */
336af8ce959Schris 
3376c4ac1deSchris static int delay_count_per_usec = 0;
338af8ce959Schris 
3396c4ac1deSchris void
calibrate_delay(void)3406c4ac1deSchris calibrate_delay(void)
3416c4ac1deSchris {
3426cfcc60eSgdamore 	/*
3436cfcc60eSgdamore 	 * For all current footbridge hardware, the fclk runs at a
3446cfcc60eSgdamore 	 * rate that is sufficiently slow enough that we don't need to
3456cfcc60eSgdamore 	 * use a prescaler.  A prescaler would be needed if the fclk
3466cfcc60eSgdamore 	 * could wrap within 2 hardclock periods (2 * HZ).  With
3476cfcc60eSgdamore 	 * normal values of HZ (100 and higher), this is unlikely to
3486cfcc60eSgdamore 	 * ever happen.
3496cfcc60eSgdamore 	 *
3506cfcc60eSgdamore 	 * We let TIMER 3 just run free, at the freqeuncy supplied by
3516cfcc60eSgdamore 	 * dc21285_fclk.
3526cfcc60eSgdamore 	 */
3536cfcc60eSgdamore 	bus_space_write_4(clock_sc->sc_iot, clock_sc->sc_ioh,
3546cfcc60eSgdamore 	    TIMER_3_BASE + TIMER_CONTROL, TIMER_ENABLE);
3556cfcc60eSgdamore 	delay_count_per_usec = dc21285_fclk / 1000000;
3566cfcc60eSgdamore 	if (dc21285_fclk % 1000000)
3576cfcc60eSgdamore 		delay_count_per_usec += 1;
3586c4ac1deSchris }
359af8ce959Schris 
360af8ce959Schris void
delay(unsigned n)3616cfcc60eSgdamore delay(unsigned n)
362af8ce959Schris {
3636c4ac1deSchris 	uint32_t cur, last, delta, usecs;
364af8ce959Schris 
3656cfcc60eSgdamore 	if (n == 0)
3666cfcc60eSgdamore 		return;
3676c4ac1deSchris 
36873ca5359Smatt 	/*
36973ca5359Smatt 	 * not calibrated the timer yet, so try to live with this horrible
37073ca5359Smatt 	 * loop!
3716cfcc60eSgdamore 	 *
3726cfcc60eSgdamore 	 * Note: a much better solution might be to have the timers
3736cfcc60eSgdamore 	 * get get calibrated out of mach_init.  Of course, the
3746cfcc60eSgdamore 	 * clock_sc needs to be set up, so we can read/write the clock
3756cfcc60eSgdamore 	 * registers.
37673ca5359Smatt 	 */
3776cfcc60eSgdamore 	if (!delay_count_per_usec)
3786c4ac1deSchris 	{
379d21e4ed1Schris 		/*
380d21e4ed1Schris 		 * the loop below has a core of 6 instructions
381d21e4ed1Schris 		 * StrongArms top out at 233Mhz, so one instruction takes
382d21e4ed1Schris 		 * 0.004 us, and 6 take 0.025 us, so we need to loop 40
383d21e4ed1Schris 		 * times to make one usec
384d21e4ed1Schris 		 */
385d21e4ed1Schris 		int delaycount = 40;
3866cfcc60eSgdamore 		volatile int i;
3876cfcc60eSgdamore 
38847c99ba5Smycroft 		while (n-- > 0) {
389af8ce959Schris 			for (i = delaycount; --i;);
3906c4ac1deSchris 		}
3916c4ac1deSchris 		return;
3926c4ac1deSchris 	}
393a8d4145fSchris 
394625d05a4Schris 	last = bus_space_read_4(clock_sc->sc_iot, clock_sc->sc_ioh,
395625d05a4Schris 	    TIMER_3_VALUE);
3966cfcc60eSgdamore 	delta = usecs = 0;
3976c4ac1deSchris 
3986cfcc60eSgdamore 	while (n > usecs) {
3996c4ac1deSchris 		cur = bus_space_read_4(clock_sc->sc_iot, clock_sc->sc_ioh,
4006c4ac1deSchris 		    TIMER_3_VALUE);
4016c4ac1deSchris 		if (last < cur)
4026c4ac1deSchris 			/* timer has wrapped */
4036cfcc60eSgdamore 			delta += ((TIMER_MAX_VAL - cur) + last);
404af8ce959Schris 		else
4056c4ac1deSchris 			delta += (last - cur);
4066c4ac1deSchris 
4076c4ac1deSchris 		last = cur;
4086cfcc60eSgdamore 
4096cfcc60eSgdamore 		while (delta >= delay_count_per_usec) {
4106cfcc60eSgdamore 			delta -= delay_count_per_usec;
4116cfcc60eSgdamore 			usecs++;
4126cfcc60eSgdamore 		}
413af8ce959Schris 	}
414af8ce959Schris }
415af8ce959Schris 
416af8ce959Schris /* End of footbridge_clock.c */
417