xref: /linux/drivers/isdn/mISDN/clock.c (revision 3bd69ad197a4a3d0085a5dc3b5796111bf176b12)
1*3bd69ad1SAndreas Eversberg /*
2*3bd69ad1SAndreas Eversberg  * Copyright 2008  by Andreas Eversberg <andreas@eversberg.eu>
3*3bd69ad1SAndreas Eversberg  *
4*3bd69ad1SAndreas Eversberg  * This program is free software; you can redistribute it and/or modify
5*3bd69ad1SAndreas Eversberg  * it under the terms of the GNU General Public License version 2 as
6*3bd69ad1SAndreas Eversberg  * published by the Free Software Foundation.
7*3bd69ad1SAndreas Eversberg  *
8*3bd69ad1SAndreas Eversberg  * This program is distributed in the hope that it will be useful,
9*3bd69ad1SAndreas Eversberg  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10*3bd69ad1SAndreas Eversberg  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11*3bd69ad1SAndreas Eversberg  * GNU General Public License for more details.
12*3bd69ad1SAndreas Eversberg  *
13*3bd69ad1SAndreas Eversberg  * Quick API description:
14*3bd69ad1SAndreas Eversberg  *
15*3bd69ad1SAndreas Eversberg  * A clock source registers using mISDN_register_clock:
16*3bd69ad1SAndreas Eversberg  * 	name = text string to name clock source
17*3bd69ad1SAndreas Eversberg  *	priority = value to priorize clock sources (0 = default)
18*3bd69ad1SAndreas Eversberg  *	ctl = callback function to enable/disable clock source
19*3bd69ad1SAndreas Eversberg  *	priv = private pointer of clock source
20*3bd69ad1SAndreas Eversberg  * 	return = pointer to clock source structure;
21*3bd69ad1SAndreas Eversberg  *
22*3bd69ad1SAndreas Eversberg  * Note: Callback 'ctl' can be called before mISDN_register_clock returns!
23*3bd69ad1SAndreas Eversberg  *       Also it can be called during mISDN_unregister_clock.
24*3bd69ad1SAndreas Eversberg  *
25*3bd69ad1SAndreas Eversberg  * A clock source calls mISDN_clock_update with given samples elapsed, if
26*3bd69ad1SAndreas Eversberg  * enabled. If function call is delayed, tv must be set with the timestamp
27*3bd69ad1SAndreas Eversberg  * of the actual event.
28*3bd69ad1SAndreas Eversberg  *
29*3bd69ad1SAndreas Eversberg  * A clock source unregisters using mISDN_unregister_clock.
30*3bd69ad1SAndreas Eversberg  *
31*3bd69ad1SAndreas Eversberg  * To get current clock, call mISDN_clock_get. The signed short value
32*3bd69ad1SAndreas Eversberg  * counts the number of samples since. Time since last clock event is added.
33*3bd69ad1SAndreas Eversberg  *
34*3bd69ad1SAndreas Eversberg  */
35*3bd69ad1SAndreas Eversberg 
36*3bd69ad1SAndreas Eversberg #include <linux/types.h>
37*3bd69ad1SAndreas Eversberg #include <linux/stddef.h>
38*3bd69ad1SAndreas Eversberg #include <linux/spinlock.h>
39*3bd69ad1SAndreas Eversberg #include <linux/mISDNif.h>
40*3bd69ad1SAndreas Eversberg #include "core.h"
41*3bd69ad1SAndreas Eversberg 
42*3bd69ad1SAndreas Eversberg static u_int *debug;
43*3bd69ad1SAndreas Eversberg static LIST_HEAD(iclock_list);
44*3bd69ad1SAndreas Eversberg DEFINE_RWLOCK(iclock_lock);
45*3bd69ad1SAndreas Eversberg u16	iclock_count;		/* counter of last clock */
46*3bd69ad1SAndreas Eversberg struct	timeval iclock_tv;	/* time stamp of last clock */
47*3bd69ad1SAndreas Eversberg int	iclock_tv_valid;	/* already received one timestamp */
48*3bd69ad1SAndreas Eversberg struct	mISDNclock *iclock_current;
49*3bd69ad1SAndreas Eversberg 
50*3bd69ad1SAndreas Eversberg void
51*3bd69ad1SAndreas Eversberg mISDN_init_clock(u_int *dp)
52*3bd69ad1SAndreas Eversberg {
53*3bd69ad1SAndreas Eversberg 	debug = dp;
54*3bd69ad1SAndreas Eversberg 	do_gettimeofday(&iclock_tv);
55*3bd69ad1SAndreas Eversberg }
56*3bd69ad1SAndreas Eversberg 
57*3bd69ad1SAndreas Eversberg static void
58*3bd69ad1SAndreas Eversberg select_iclock(void)
59*3bd69ad1SAndreas Eversberg {
60*3bd69ad1SAndreas Eversberg 	struct mISDNclock *iclock, *bestclock = NULL, *lastclock = NULL;
61*3bd69ad1SAndreas Eversberg 	int pri = -128;
62*3bd69ad1SAndreas Eversberg 
63*3bd69ad1SAndreas Eversberg 	list_for_each_entry(iclock, &iclock_list, list) {
64*3bd69ad1SAndreas Eversberg 		if (iclock->pri > pri) {
65*3bd69ad1SAndreas Eversberg 			pri = iclock->pri;
66*3bd69ad1SAndreas Eversberg 			bestclock = iclock;
67*3bd69ad1SAndreas Eversberg 		}
68*3bd69ad1SAndreas Eversberg 		if (iclock_current == iclock)
69*3bd69ad1SAndreas Eversberg 			lastclock = iclock;
70*3bd69ad1SAndreas Eversberg 	}
71*3bd69ad1SAndreas Eversberg 	if (lastclock && bestclock != lastclock) {
72*3bd69ad1SAndreas Eversberg 		/* last used clock source still exists but changes, disable */
73*3bd69ad1SAndreas Eversberg 		if (*debug & DEBUG_CLOCK)
74*3bd69ad1SAndreas Eversberg 			printk(KERN_DEBUG "Old clock source '%s' disable.\n",
75*3bd69ad1SAndreas Eversberg 				lastclock->name);
76*3bd69ad1SAndreas Eversberg 		lastclock->ctl(lastclock->priv, 0);
77*3bd69ad1SAndreas Eversberg 	}
78*3bd69ad1SAndreas Eversberg 	if (bestclock && bestclock != iclock_current) {
79*3bd69ad1SAndreas Eversberg 		/* new clock source selected, enable */
80*3bd69ad1SAndreas Eversberg 		if (*debug & DEBUG_CLOCK)
81*3bd69ad1SAndreas Eversberg 			printk(KERN_DEBUG "New clock source '%s' enable.\n",
82*3bd69ad1SAndreas Eversberg 				bestclock->name);
83*3bd69ad1SAndreas Eversberg 		bestclock->ctl(bestclock->priv, 1);
84*3bd69ad1SAndreas Eversberg 	}
85*3bd69ad1SAndreas Eversberg 	if (bestclock != iclock_current) {
86*3bd69ad1SAndreas Eversberg 		/* no clock received yet */
87*3bd69ad1SAndreas Eversberg 		iclock_tv_valid = 0;
88*3bd69ad1SAndreas Eversberg 	}
89*3bd69ad1SAndreas Eversberg 	iclock_current = bestclock;
90*3bd69ad1SAndreas Eversberg }
91*3bd69ad1SAndreas Eversberg 
92*3bd69ad1SAndreas Eversberg struct mISDNclock
93*3bd69ad1SAndreas Eversberg *mISDN_register_clock(char *name, int pri, clockctl_func_t *ctl, void *priv)
94*3bd69ad1SAndreas Eversberg {
95*3bd69ad1SAndreas Eversberg 	u_long			flags;
96*3bd69ad1SAndreas Eversberg 	struct mISDNclock	*iclock;
97*3bd69ad1SAndreas Eversberg 
98*3bd69ad1SAndreas Eversberg 	if (*debug & (DEBUG_CORE | DEBUG_CLOCK))
99*3bd69ad1SAndreas Eversberg 		printk(KERN_DEBUG "%s: %s %d\n", __func__, name, pri);
100*3bd69ad1SAndreas Eversberg 	iclock = kzalloc(sizeof(struct mISDNclock), GFP_ATOMIC);
101*3bd69ad1SAndreas Eversberg 	if (!iclock) {
102*3bd69ad1SAndreas Eversberg 		printk(KERN_ERR "%s: No memory for clock entry.\n", __func__);
103*3bd69ad1SAndreas Eversberg 		return NULL;
104*3bd69ad1SAndreas Eversberg 	}
105*3bd69ad1SAndreas Eversberg 	strncpy(iclock->name, name, sizeof(iclock->name)-1);
106*3bd69ad1SAndreas Eversberg 	iclock->pri = pri;
107*3bd69ad1SAndreas Eversberg 	iclock->priv = priv;
108*3bd69ad1SAndreas Eversberg 	iclock->ctl = ctl;
109*3bd69ad1SAndreas Eversberg 	write_lock_irqsave(&iclock_lock, flags);
110*3bd69ad1SAndreas Eversberg 	list_add_tail(&iclock->list, &iclock_list);
111*3bd69ad1SAndreas Eversberg 	select_iclock();
112*3bd69ad1SAndreas Eversberg 	write_unlock_irqrestore(&iclock_lock, flags);
113*3bd69ad1SAndreas Eversberg 	return iclock;
114*3bd69ad1SAndreas Eversberg }
115*3bd69ad1SAndreas Eversberg EXPORT_SYMBOL(mISDN_register_clock);
116*3bd69ad1SAndreas Eversberg 
117*3bd69ad1SAndreas Eversberg void
118*3bd69ad1SAndreas Eversberg mISDN_unregister_clock(struct mISDNclock *iclock)
119*3bd69ad1SAndreas Eversberg {
120*3bd69ad1SAndreas Eversberg 	u_long	flags;
121*3bd69ad1SAndreas Eversberg 
122*3bd69ad1SAndreas Eversberg 	if (*debug & (DEBUG_CORE | DEBUG_CLOCK))
123*3bd69ad1SAndreas Eversberg 		printk(KERN_DEBUG "%s: %s %d\n", __func__, iclock->name,
124*3bd69ad1SAndreas Eversberg 			iclock->pri);
125*3bd69ad1SAndreas Eversberg 	write_lock_irqsave(&iclock_lock, flags);
126*3bd69ad1SAndreas Eversberg 	if (iclock_current == iclock) {
127*3bd69ad1SAndreas Eversberg 		if (*debug & DEBUG_CLOCK)
128*3bd69ad1SAndreas Eversberg 			printk(KERN_DEBUG
129*3bd69ad1SAndreas Eversberg 				"Current clock source '%s' unregisters.\n",
130*3bd69ad1SAndreas Eversberg 				iclock->name);
131*3bd69ad1SAndreas Eversberg 		iclock->ctl(iclock->priv, 0);
132*3bd69ad1SAndreas Eversberg 	}
133*3bd69ad1SAndreas Eversberg 	list_del(&iclock->list);
134*3bd69ad1SAndreas Eversberg 	select_iclock();
135*3bd69ad1SAndreas Eversberg 	write_unlock_irqrestore(&iclock_lock, flags);
136*3bd69ad1SAndreas Eversberg }
137*3bd69ad1SAndreas Eversberg EXPORT_SYMBOL(mISDN_unregister_clock);
138*3bd69ad1SAndreas Eversberg 
139*3bd69ad1SAndreas Eversberg void
140*3bd69ad1SAndreas Eversberg mISDN_clock_update(struct mISDNclock *iclock, int samples, struct timeval *tv)
141*3bd69ad1SAndreas Eversberg {
142*3bd69ad1SAndreas Eversberg 	u_long		flags;
143*3bd69ad1SAndreas Eversberg 	struct timeval	tv_now;
144*3bd69ad1SAndreas Eversberg 	time_t		elapsed_sec;
145*3bd69ad1SAndreas Eversberg 	int		elapsed_8000th;
146*3bd69ad1SAndreas Eversberg 
147*3bd69ad1SAndreas Eversberg 	write_lock_irqsave(&iclock_lock, flags);
148*3bd69ad1SAndreas Eversberg 	if (iclock_current != iclock) {
149*3bd69ad1SAndreas Eversberg 		printk(KERN_ERR "%s: '%s' sends us clock updates, but we do "
150*3bd69ad1SAndreas Eversberg 			"listen to '%s'. This is a bug!\n", __func__,
151*3bd69ad1SAndreas Eversberg 			iclock->name,
152*3bd69ad1SAndreas Eversberg 			iclock_current ? iclock_current->name : "nothing");
153*3bd69ad1SAndreas Eversberg 		iclock->ctl(iclock->priv, 0);
154*3bd69ad1SAndreas Eversberg 		write_unlock_irqrestore(&iclock_lock, flags);
155*3bd69ad1SAndreas Eversberg 		return;
156*3bd69ad1SAndreas Eversberg 	}
157*3bd69ad1SAndreas Eversberg 	if (iclock_tv_valid) {
158*3bd69ad1SAndreas Eversberg 		/* increment sample counter by given samples */
159*3bd69ad1SAndreas Eversberg 		iclock_count += samples;
160*3bd69ad1SAndreas Eversberg 		if (tv) { /* tv must be set, if function call is delayed */
161*3bd69ad1SAndreas Eversberg 			iclock_tv.tv_sec = tv->tv_sec;
162*3bd69ad1SAndreas Eversberg 			iclock_tv.tv_usec = tv->tv_usec;
163*3bd69ad1SAndreas Eversberg 		} else
164*3bd69ad1SAndreas Eversberg 			do_gettimeofday(&iclock_tv);
165*3bd69ad1SAndreas Eversberg 	} else {
166*3bd69ad1SAndreas Eversberg 		/* calc elapsed time by system clock */
167*3bd69ad1SAndreas Eversberg 		if (tv) { /* tv must be set, if function call is delayed */
168*3bd69ad1SAndreas Eversberg 			tv_now.tv_sec = tv->tv_sec;
169*3bd69ad1SAndreas Eversberg 			tv_now.tv_usec = tv->tv_usec;
170*3bd69ad1SAndreas Eversberg 		} else
171*3bd69ad1SAndreas Eversberg 			do_gettimeofday(&tv_now);
172*3bd69ad1SAndreas Eversberg 		elapsed_sec = tv_now.tv_sec - iclock_tv.tv_sec;
173*3bd69ad1SAndreas Eversberg 		elapsed_8000th = (tv_now.tv_usec / 125)
174*3bd69ad1SAndreas Eversberg 			- (iclock_tv.tv_usec / 125);
175*3bd69ad1SAndreas Eversberg 		if (elapsed_8000th < 0) {
176*3bd69ad1SAndreas Eversberg 			elapsed_sec -= 1;
177*3bd69ad1SAndreas Eversberg 			elapsed_8000th += 8000;
178*3bd69ad1SAndreas Eversberg 		}
179*3bd69ad1SAndreas Eversberg 		/* add elapsed time to counter and set new timestamp */
180*3bd69ad1SAndreas Eversberg 		iclock_count += elapsed_sec * 8000 + elapsed_8000th;
181*3bd69ad1SAndreas Eversberg 		iclock_tv.tv_sec = tv_now.tv_sec;
182*3bd69ad1SAndreas Eversberg 		iclock_tv.tv_usec = tv_now.tv_usec;
183*3bd69ad1SAndreas Eversberg 		iclock_tv_valid = 1;
184*3bd69ad1SAndreas Eversberg 		if (*debug & DEBUG_CLOCK)
185*3bd69ad1SAndreas Eversberg 			printk("Received first clock from source '%s'.\n",
186*3bd69ad1SAndreas Eversberg 			    iclock_current ? iclock_current->name : "nothing");
187*3bd69ad1SAndreas Eversberg 	}
188*3bd69ad1SAndreas Eversberg 	write_unlock_irqrestore(&iclock_lock, flags);
189*3bd69ad1SAndreas Eversberg }
190*3bd69ad1SAndreas Eversberg EXPORT_SYMBOL(mISDN_clock_update);
191*3bd69ad1SAndreas Eversberg 
192*3bd69ad1SAndreas Eversberg unsigned short
193*3bd69ad1SAndreas Eversberg mISDN_clock_get(void)
194*3bd69ad1SAndreas Eversberg {
195*3bd69ad1SAndreas Eversberg 	u_long		flags;
196*3bd69ad1SAndreas Eversberg 	struct timeval	tv_now;
197*3bd69ad1SAndreas Eversberg 	time_t		elapsed_sec;
198*3bd69ad1SAndreas Eversberg 	int		elapsed_8000th;
199*3bd69ad1SAndreas Eversberg 	u16		count;
200*3bd69ad1SAndreas Eversberg 
201*3bd69ad1SAndreas Eversberg 	read_lock_irqsave(&iclock_lock, flags);
202*3bd69ad1SAndreas Eversberg 	/* calc elapsed time by system clock */
203*3bd69ad1SAndreas Eversberg 	do_gettimeofday(&tv_now);
204*3bd69ad1SAndreas Eversberg 	elapsed_sec = tv_now.tv_sec - iclock_tv.tv_sec;
205*3bd69ad1SAndreas Eversberg 	elapsed_8000th = (tv_now.tv_usec / 125) - (iclock_tv.tv_usec / 125);
206*3bd69ad1SAndreas Eversberg 	if (elapsed_8000th < 0) {
207*3bd69ad1SAndreas Eversberg 		elapsed_sec -= 1;
208*3bd69ad1SAndreas Eversberg 		elapsed_8000th += 8000;
209*3bd69ad1SAndreas Eversberg 	}
210*3bd69ad1SAndreas Eversberg 	/* add elapsed time to counter */
211*3bd69ad1SAndreas Eversberg 	count =	iclock_count + elapsed_sec * 8000 + elapsed_8000th;
212*3bd69ad1SAndreas Eversberg 	read_unlock_irqrestore(&iclock_lock, flags);
213*3bd69ad1SAndreas Eversberg 	return count;
214*3bd69ad1SAndreas Eversberg }
215*3bd69ad1SAndreas Eversberg EXPORT_SYMBOL(mISDN_clock_get);
216*3bd69ad1SAndreas Eversberg 
217