162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright 2008 by Andreas Eversberg <andreas@eversberg.eu> 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Quick API description: 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * A clock source registers using mISDN_register_clock: 862306a36Sopenharmony_ci * name = text string to name clock source 962306a36Sopenharmony_ci * priority = value to priorize clock sources (0 = default) 1062306a36Sopenharmony_ci * ctl = callback function to enable/disable clock source 1162306a36Sopenharmony_ci * priv = private pointer of clock source 1262306a36Sopenharmony_ci * return = pointer to clock source structure; 1362306a36Sopenharmony_ci * 1462306a36Sopenharmony_ci * Note: Callback 'ctl' can be called before mISDN_register_clock returns! 1562306a36Sopenharmony_ci * Also it can be called during mISDN_unregister_clock. 1662306a36Sopenharmony_ci * 1762306a36Sopenharmony_ci * A clock source calls mISDN_clock_update with given samples elapsed, if 1862306a36Sopenharmony_ci * enabled. If function call is delayed, tv must be set with the timestamp 1962306a36Sopenharmony_ci * of the actual event. 2062306a36Sopenharmony_ci * 2162306a36Sopenharmony_ci * A clock source unregisters using mISDN_unregister_clock. 2262306a36Sopenharmony_ci * 2362306a36Sopenharmony_ci * To get current clock, call mISDN_clock_get. The signed short value 2462306a36Sopenharmony_ci * counts the number of samples since. Time since last clock event is added. 2562306a36Sopenharmony_ci */ 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci#include <linux/slab.h> 2862306a36Sopenharmony_ci#include <linux/types.h> 2962306a36Sopenharmony_ci#include <linux/stddef.h> 3062306a36Sopenharmony_ci#include <linux/spinlock.h> 3162306a36Sopenharmony_ci#include <linux/ktime.h> 3262306a36Sopenharmony_ci#include <linux/mISDNif.h> 3362306a36Sopenharmony_ci#include <linux/export.h> 3462306a36Sopenharmony_ci#include "core.h" 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_cistatic u_int *debug; 3762306a36Sopenharmony_cistatic LIST_HEAD(iclock_list); 3862306a36Sopenharmony_cistatic DEFINE_RWLOCK(iclock_lock); 3962306a36Sopenharmony_cistatic u16 iclock_count; /* counter of last clock */ 4062306a36Sopenharmony_cistatic ktime_t iclock_timestamp; /* time stamp of last clock */ 4162306a36Sopenharmony_cistatic int iclock_timestamp_valid; /* already received one timestamp */ 4262306a36Sopenharmony_cistatic struct mISDNclock *iclock_current; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_civoid 4562306a36Sopenharmony_cimISDN_init_clock(u_int *dp) 4662306a36Sopenharmony_ci{ 4762306a36Sopenharmony_ci debug = dp; 4862306a36Sopenharmony_ci iclock_timestamp = ktime_get(); 4962306a36Sopenharmony_ci} 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_cistatic void 5262306a36Sopenharmony_ciselect_iclock(void) 5362306a36Sopenharmony_ci{ 5462306a36Sopenharmony_ci struct mISDNclock *iclock, *bestclock = NULL, *lastclock = NULL; 5562306a36Sopenharmony_ci int pri = -128; 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci list_for_each_entry(iclock, &iclock_list, list) { 5862306a36Sopenharmony_ci if (iclock->pri > pri) { 5962306a36Sopenharmony_ci pri = iclock->pri; 6062306a36Sopenharmony_ci bestclock = iclock; 6162306a36Sopenharmony_ci } 6262306a36Sopenharmony_ci if (iclock_current == iclock) 6362306a36Sopenharmony_ci lastclock = iclock; 6462306a36Sopenharmony_ci } 6562306a36Sopenharmony_ci if (lastclock && bestclock != lastclock) { 6662306a36Sopenharmony_ci /* last used clock source still exists but changes, disable */ 6762306a36Sopenharmony_ci if (*debug & DEBUG_CLOCK) 6862306a36Sopenharmony_ci printk(KERN_DEBUG "Old clock source '%s' disable.\n", 6962306a36Sopenharmony_ci lastclock->name); 7062306a36Sopenharmony_ci lastclock->ctl(lastclock->priv, 0); 7162306a36Sopenharmony_ci } 7262306a36Sopenharmony_ci if (bestclock && bestclock != iclock_current) { 7362306a36Sopenharmony_ci /* new clock source selected, enable */ 7462306a36Sopenharmony_ci if (*debug & DEBUG_CLOCK) 7562306a36Sopenharmony_ci printk(KERN_DEBUG "New clock source '%s' enable.\n", 7662306a36Sopenharmony_ci bestclock->name); 7762306a36Sopenharmony_ci bestclock->ctl(bestclock->priv, 1); 7862306a36Sopenharmony_ci } 7962306a36Sopenharmony_ci if (bestclock != iclock_current) { 8062306a36Sopenharmony_ci /* no clock received yet */ 8162306a36Sopenharmony_ci iclock_timestamp_valid = 0; 8262306a36Sopenharmony_ci } 8362306a36Sopenharmony_ci iclock_current = bestclock; 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistruct mISDNclock 8762306a36Sopenharmony_ci*mISDN_register_clock(char *name, int pri, clockctl_func_t *ctl, void *priv) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci u_long flags; 9062306a36Sopenharmony_ci struct mISDNclock *iclock; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci if (*debug & (DEBUG_CORE | DEBUG_CLOCK)) 9362306a36Sopenharmony_ci printk(KERN_DEBUG "%s: %s %d\n", __func__, name, pri); 9462306a36Sopenharmony_ci iclock = kzalloc(sizeof(struct mISDNclock), GFP_ATOMIC); 9562306a36Sopenharmony_ci if (!iclock) { 9662306a36Sopenharmony_ci printk(KERN_ERR "%s: No memory for clock entry.\n", __func__); 9762306a36Sopenharmony_ci return NULL; 9862306a36Sopenharmony_ci } 9962306a36Sopenharmony_ci strncpy(iclock->name, name, sizeof(iclock->name) - 1); 10062306a36Sopenharmony_ci iclock->pri = pri; 10162306a36Sopenharmony_ci iclock->priv = priv; 10262306a36Sopenharmony_ci iclock->ctl = ctl; 10362306a36Sopenharmony_ci write_lock_irqsave(&iclock_lock, flags); 10462306a36Sopenharmony_ci list_add_tail(&iclock->list, &iclock_list); 10562306a36Sopenharmony_ci select_iclock(); 10662306a36Sopenharmony_ci write_unlock_irqrestore(&iclock_lock, flags); 10762306a36Sopenharmony_ci return iclock; 10862306a36Sopenharmony_ci} 10962306a36Sopenharmony_ciEXPORT_SYMBOL(mISDN_register_clock); 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_civoid 11262306a36Sopenharmony_cimISDN_unregister_clock(struct mISDNclock *iclock) 11362306a36Sopenharmony_ci{ 11462306a36Sopenharmony_ci u_long flags; 11562306a36Sopenharmony_ci 11662306a36Sopenharmony_ci if (*debug & (DEBUG_CORE | DEBUG_CLOCK)) 11762306a36Sopenharmony_ci printk(KERN_DEBUG "%s: %s %d\n", __func__, iclock->name, 11862306a36Sopenharmony_ci iclock->pri); 11962306a36Sopenharmony_ci write_lock_irqsave(&iclock_lock, flags); 12062306a36Sopenharmony_ci if (iclock_current == iclock) { 12162306a36Sopenharmony_ci if (*debug & DEBUG_CLOCK) 12262306a36Sopenharmony_ci printk(KERN_DEBUG 12362306a36Sopenharmony_ci "Current clock source '%s' unregisters.\n", 12462306a36Sopenharmony_ci iclock->name); 12562306a36Sopenharmony_ci iclock->ctl(iclock->priv, 0); 12662306a36Sopenharmony_ci } 12762306a36Sopenharmony_ci list_del(&iclock->list); 12862306a36Sopenharmony_ci select_iclock(); 12962306a36Sopenharmony_ci write_unlock_irqrestore(&iclock_lock, flags); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ciEXPORT_SYMBOL(mISDN_unregister_clock); 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_civoid 13462306a36Sopenharmony_cimISDN_clock_update(struct mISDNclock *iclock, int samples, ktime_t *timestamp) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci u_long flags; 13762306a36Sopenharmony_ci ktime_t timestamp_now; 13862306a36Sopenharmony_ci u16 delta; 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci write_lock_irqsave(&iclock_lock, flags); 14162306a36Sopenharmony_ci if (iclock_current != iclock) { 14262306a36Sopenharmony_ci printk(KERN_ERR "%s: '%s' sends us clock updates, but we do " 14362306a36Sopenharmony_ci "listen to '%s'. This is a bug!\n", __func__, 14462306a36Sopenharmony_ci iclock->name, 14562306a36Sopenharmony_ci iclock_current ? iclock_current->name : "nothing"); 14662306a36Sopenharmony_ci iclock->ctl(iclock->priv, 0); 14762306a36Sopenharmony_ci write_unlock_irqrestore(&iclock_lock, flags); 14862306a36Sopenharmony_ci return; 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci if (iclock_timestamp_valid) { 15162306a36Sopenharmony_ci /* increment sample counter by given samples */ 15262306a36Sopenharmony_ci iclock_count += samples; 15362306a36Sopenharmony_ci if (timestamp) { /* timestamp must be set, if function call is delayed */ 15462306a36Sopenharmony_ci iclock_timestamp = *timestamp; 15562306a36Sopenharmony_ci } else { 15662306a36Sopenharmony_ci iclock_timestamp = ktime_get(); 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci } else { 15962306a36Sopenharmony_ci /* calc elapsed time by system clock */ 16062306a36Sopenharmony_ci if (timestamp) { /* timestamp must be set, if function call is delayed */ 16162306a36Sopenharmony_ci timestamp_now = *timestamp; 16262306a36Sopenharmony_ci } else { 16362306a36Sopenharmony_ci timestamp_now = ktime_get(); 16462306a36Sopenharmony_ci } 16562306a36Sopenharmony_ci delta = ktime_divns(ktime_sub(timestamp_now, iclock_timestamp), 16662306a36Sopenharmony_ci (NSEC_PER_SEC / 8000)); 16762306a36Sopenharmony_ci /* add elapsed time to counter and set new timestamp */ 16862306a36Sopenharmony_ci iclock_count += delta; 16962306a36Sopenharmony_ci iclock_timestamp = timestamp_now; 17062306a36Sopenharmony_ci iclock_timestamp_valid = 1; 17162306a36Sopenharmony_ci if (*debug & DEBUG_CLOCK) 17262306a36Sopenharmony_ci printk("Received first clock from source '%s'.\n", 17362306a36Sopenharmony_ci iclock_current ? iclock_current->name : "nothing"); 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci write_unlock_irqrestore(&iclock_lock, flags); 17662306a36Sopenharmony_ci} 17762306a36Sopenharmony_ciEXPORT_SYMBOL(mISDN_clock_update); 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ciunsigned short 18062306a36Sopenharmony_cimISDN_clock_get(void) 18162306a36Sopenharmony_ci{ 18262306a36Sopenharmony_ci u_long flags; 18362306a36Sopenharmony_ci ktime_t timestamp_now; 18462306a36Sopenharmony_ci u16 delta; 18562306a36Sopenharmony_ci u16 count; 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci read_lock_irqsave(&iclock_lock, flags); 18862306a36Sopenharmony_ci /* calc elapsed time by system clock */ 18962306a36Sopenharmony_ci timestamp_now = ktime_get(); 19062306a36Sopenharmony_ci delta = ktime_divns(ktime_sub(timestamp_now, iclock_timestamp), 19162306a36Sopenharmony_ci (NSEC_PER_SEC / 8000)); 19262306a36Sopenharmony_ci /* add elapsed time to counter */ 19362306a36Sopenharmony_ci count = iclock_count + delta; 19462306a36Sopenharmony_ci read_unlock_irqrestore(&iclock_lock, flags); 19562306a36Sopenharmony_ci return count; 19662306a36Sopenharmony_ci} 19762306a36Sopenharmony_ciEXPORT_SYMBOL(mISDN_clock_get); 198