mISDN: Add ISDN sample clock API to mISDN core
authorAndreas Eversberg <andreas@eversberg.eu>
Sat, 6 Sep 2008 07:03:46 +0000 (09:03 +0200)
committerKarsten Keil <kkeil@suse.de>
Fri, 9 Jan 2009 21:44:27 +0000 (22:44 +0100)
Add ISDN sample clock API to mISDN core (new file clock.c)
hfcmulti and mISDNdsp use clock API.

Signed-off-by: Andreas Eversberg <andreas@eversberg.eu>
Signed-off-by: Karsten Keil <kkeil@suse.de>
drivers/isdn/hardware/mISDN/hfc_multi.h
drivers/isdn/hardware/mISDN/hfcmulti.c
drivers/isdn/mISDN/Makefile
drivers/isdn/mISDN/clock.c [new file with mode: 0644]
drivers/isdn/mISDN/core.c
drivers/isdn/mISDN/core.h
drivers/isdn/mISDN/dsp_cmx.c
include/linux/mISDNif.h

index 4aa6a8b41f507290b957a4aad4ef342afa6f1564..663b77f578bea047cf134aee7bc739622a2cf910 100644 (file)
@@ -197,6 +197,9 @@ struct hfc_multi {
 
        spinlock_t      lock;   /* the lock */
 
+       struct mISDNclock *iclock; /* isdn clock support */
+       int             iclock_on;
+
        /*
         * the channel index is counted from 0, regardless where the channel
         * is located on the hfc-channel.
index 3fc2e9d953413f11ede05c82979ca57de2c10bc6..592db93105f957e1c98bb9addd198567ccdee21d 100644 (file)
  *     Give the value of the clock control register (A_ST_CLK_DLY)
  *     of the S/T interfaces in TE mode.
  *     This register is needed for the TBR3 certification, so don't change it.
+ *
+ * clock:
+ *     NOTE: only one clockdelay_te value must be given once
+ *     Selects interface with clock source for mISDN and applications.
+ *     Set to card number starting with 1. Set to -1 to disable.
+ *     By default, the first card is used as clock source.
  */
 
 /*
@@ -190,12 +196,13 @@ static int nt_t1_count[] = { 3840, 1920, 960, 480, 240, 120, 60, 30  };
  */
 
 static uint    type[MAX_CARDS];
-static uint    pcm[MAX_CARDS];
-static uint    dslot[MAX_CARDS];
+static int     pcm[MAX_CARDS];
+static int     dslot[MAX_CARDS];
 static uint    iomode[MAX_CARDS];
 static uint    port[MAX_PORTS];
 static uint    debug;
 static uint    poll;
+static int     clock;
 static uint    timer;
 static uint    clockdelay_te = CLKDEL_TE;
 static uint    clockdelay_nt = CLKDEL_NT;
@@ -207,12 +214,13 @@ MODULE_LICENSE("GPL");
 MODULE_VERSION(HFC_MULTI_VERSION);
 module_param(debug, uint, S_IRUGO | S_IWUSR);
 module_param(poll, uint, S_IRUGO | S_IWUSR);
+module_param(clock, int, S_IRUGO | S_IWUSR);
 module_param(timer, uint, S_IRUGO | S_IWUSR);
 module_param(clockdelay_te, uint, S_IRUGO | S_IWUSR);
 module_param(clockdelay_nt, uint, S_IRUGO | S_IWUSR);
 module_param_array(type, uint, NULL, S_IRUGO | S_IWUSR);
-module_param_array(pcm, uint, NULL, S_IRUGO | S_IWUSR);
-module_param_array(dslot, uint, NULL, S_IRUGO | S_IWUSR);
+module_param_array(pcm, int, NULL, S_IRUGO | S_IWUSR);
+module_param_array(dslot, int, NULL, S_IRUGO | S_IWUSR);
 module_param_array(iomode, uint, NULL, S_IRUGO | S_IWUSR);
 module_param_array(port, uint, NULL, S_IRUGO | S_IWUSR);
 
@@ -2629,6 +2637,7 @@ hfcmulti_interrupt(int intno, void *dev_id)
                iqcnt = 0;
        }
 #endif
+
        if (!r_irq_statech &&
            !(status & (V_DTMF_STA | V_LOST_STA | V_EXT_IRQSTA |
            V_MISC_IRQSTA | V_FR_IRQSTA))) {
@@ -2683,11 +2692,13 @@ hfcmulti_interrupt(int intno, void *dev_id)
                                        plxsd_checksync(hc, 0);
                        }
                }
-               if (r_irq_misc & V_TI_IRQ)
+               if (r_irq_misc & V_TI_IRQ) {
+                       if (hc->iclock_on)
+                               mISDN_clock_update(hc->iclock, poll, NULL);
                        handle_timer_irq(hc);
+               }
 
                if (r_irq_misc & V_DTMF_IRQ) {
-                       /* -> DTMF IRQ */
                        hfcmulti_dtmf(hc);
                }
                if (r_irq_misc & V_IRQ_PROC) {
@@ -4075,6 +4086,15 @@ hfcm_dctrl(struct mISDNchannel *ch, u_int cmd, void *arg)
        return err;
 }
 
+static int
+clockctl(void *priv, int enable)
+{
+       struct hfc_multi *hc = priv;
+
+       hc->iclock_on = enable;
+       return 0;
+}
+
 /*
  * initialize the card
  */
@@ -4489,10 +4509,14 @@ release_card(struct hfc_multi *hc)
                printk(KERN_WARNING "%s: release card (%d) entered\n",
                    __func__, hc->id);
 
+       /* unregister clock source */
+       if (hc->iclock)
+               mISDN_unregister_clock(hc->iclock);
+
+       /* disable irq */
        spin_lock_irqsave(&hc->lock, flags);
        disable_hwirq(hc);
        spin_unlock_irqrestore(&hc->lock, flags);
-
        udelay(1000);
 
        /* dimm leds */
@@ -5003,6 +5027,10 @@ hfcmulti_init(struct pci_dev *pdev, const struct pci_device_id *ent)
        list_add_tail(&hc->list, &HFClist);
        spin_unlock_irqrestore(&HFClock, flags);
 
+       /* use as clock source */
+       if (clock == HFC_cnt + 1)
+               hc->iclock = mISDN_register_clock("HFCMulti", 0, clockctl, hc);
+
        /* initialize hardware */
        ret_err = init_card(hc);
        if (ret_err) {
@@ -5273,6 +5301,9 @@ HFCmulti_init(void)
 
        }
 
+       if (!clock)
+               clock = 1;
+
        err = pci_register_driver(&hfcmultipci_driver);
        if (err < 0) {
                printk(KERN_ERR "error registering pci driver: %x\n", err);
index 1cb5e633cf75b4f22c9341e45cc297cd051d3e97..0a6bd2a9e730d5f0326f4f8c7152a95b890ed912 100644 (file)
@@ -8,6 +8,6 @@ obj-$(CONFIG_MISDN_L1OIP) += l1oip.o
 
 # multi objects
 
-mISDN_core-objs := core.o fsm.o socket.o hwchannel.o stack.o layer1.o layer2.o tei.o timerdev.o
+mISDN_core-objs := core.o fsm.o socket.o clock.o hwchannel.o stack.o layer1.o layer2.o tei.o timerdev.o
 mISDN_dsp-objs := dsp_core.o dsp_cmx.o dsp_tones.o dsp_dtmf.o dsp_audio.o dsp_blowfish.o dsp_pipeline.o dsp_hwec.o
 l1oip-objs := l1oip_core.o l1oip_codec.o
diff --git a/drivers/isdn/mISDN/clock.c b/drivers/isdn/mISDN/clock.c
new file mode 100644 (file)
index 0000000..44d9c3d
--- /dev/null
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2008  by Andreas Eversberg <andreas@eversberg.eu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * Quick API description:
+ *
+ * A clock source registers using mISDN_register_clock:
+ *     name = text string to name clock source
+ *     priority = value to priorize clock sources (0 = default)
+ *     ctl = callback function to enable/disable clock source
+ *     priv = private pointer of clock source
+ *     return = pointer to clock source structure;
+ *
+ * Note: Callback 'ctl' can be called before mISDN_register_clock returns!
+ *       Also it can be called during mISDN_unregister_clock.
+ *
+ * A clock source calls mISDN_clock_update with given samples elapsed, if
+ * enabled. If function call is delayed, tv must be set with the timestamp
+ * of the actual event.
+ *
+ * A clock source unregisters using mISDN_unregister_clock.
+ *
+ * To get current clock, call mISDN_clock_get. The signed short value
+ * counts the number of samples since. Time since last clock event is added.
+ *
+ */
+
+#include <linux/types.h>
+#include <linux/stddef.h>
+#include <linux/spinlock.h>
+#include <linux/mISDNif.h>
+#include "core.h"
+
+static u_int *debug;
+static LIST_HEAD(iclock_list);
+DEFINE_RWLOCK(iclock_lock);
+u16    iclock_count;           /* counter of last clock */
+struct timeval iclock_tv;      /* time stamp of last clock */
+int    iclock_tv_valid;        /* already received one timestamp */
+struct mISDNclock *iclock_current;
+
+void
+mISDN_init_clock(u_int *dp)
+{
+       debug = dp;
+       do_gettimeofday(&iclock_tv);
+}
+
+static void
+select_iclock(void)
+{
+       struct mISDNclock *iclock, *bestclock = NULL, *lastclock = NULL;
+       int pri = -128;
+
+       list_for_each_entry(iclock, &iclock_list, list) {
+               if (iclock->pri > pri) {
+                       pri = iclock->pri;
+                       bestclock = iclock;
+               }
+               if (iclock_current == iclock)
+                       lastclock = iclock;
+       }
+       if (lastclock && bestclock != lastclock) {
+               /* last used clock source still exists but changes, disable */
+               if (*debug & DEBUG_CLOCK)
+                       printk(KERN_DEBUG "Old clock source '%s' disable.\n",
+                               lastclock->name);
+               lastclock->ctl(lastclock->priv, 0);
+       }
+       if (bestclock && bestclock != iclock_current) {
+               /* new clock source selected, enable */
+               if (*debug & DEBUG_CLOCK)
+                       printk(KERN_DEBUG "New clock source '%s' enable.\n",
+                               bestclock->name);
+               bestclock->ctl(bestclock->priv, 1);
+       }
+       if (bestclock != iclock_current) {
+               /* no clock received yet */
+               iclock_tv_valid = 0;
+       }
+       iclock_current = bestclock;
+}
+
+struct mISDNclock
+*mISDN_register_clock(char *name, int pri, clockctl_func_t *ctl, void *priv)
+{
+       u_long                  flags;
+       struct mISDNclock       *iclock;
+
+       if (*debug & (DEBUG_CORE | DEBUG_CLOCK))
+               printk(KERN_DEBUG "%s: %s %d\n", __func__, name, pri);
+       iclock = kzalloc(sizeof(struct mISDNclock), GFP_ATOMIC);
+       if (!iclock) {
+               printk(KERN_ERR "%s: No memory for clock entry.\n", __func__);
+               return NULL;
+       }
+       strncpy(iclock->name, name, sizeof(iclock->name)-1);
+       iclock->pri = pri;
+       iclock->priv = priv;
+       iclock->ctl = ctl;
+       write_lock_irqsave(&iclock_lock, flags);
+       list_add_tail(&iclock->list, &iclock_list);
+       select_iclock();
+       write_unlock_irqrestore(&iclock_lock, flags);
+       return iclock;
+}
+EXPORT_SYMBOL(mISDN_register_clock);
+
+void
+mISDN_unregister_clock(struct mISDNclock *iclock)
+{
+       u_long  flags;
+
+       if (*debug & (DEBUG_CORE | DEBUG_CLOCK))
+               printk(KERN_DEBUG "%s: %s %d\n", __func__, iclock->name,
+                       iclock->pri);
+       write_lock_irqsave(&iclock_lock, flags);
+       if (iclock_current == iclock) {
+               if (*debug & DEBUG_CLOCK)
+                       printk(KERN_DEBUG
+                               "Current clock source '%s' unregisters.\n",
+                               iclock->name);
+               iclock->ctl(iclock->priv, 0);
+       }
+       list_del(&iclock->list);
+       select_iclock();
+       write_unlock_irqrestore(&iclock_lock, flags);
+}
+EXPORT_SYMBOL(mISDN_unregister_clock);
+
+void
+mISDN_clock_update(struct mISDNclock *iclock, int samples, struct timeval *tv)
+{
+       u_long          flags;
+       struct timeval  tv_now;
+       time_t          elapsed_sec;
+       int             elapsed_8000th;
+
+       write_lock_irqsave(&iclock_lock, flags);
+       if (iclock_current != iclock) {
+               printk(KERN_ERR "%s: '%s' sends us clock updates, but we do "
+                       "listen to '%s'. This is a bug!\n", __func__,
+                       iclock->name,
+                       iclock_current ? iclock_current->name : "nothing");
+               iclock->ctl(iclock->priv, 0);
+               write_unlock_irqrestore(&iclock_lock, flags);
+               return;
+       }
+       if (iclock_tv_valid) {
+               /* increment sample counter by given samples */
+               iclock_count += samples;
+               if (tv) { /* tv must be set, if function call is delayed */
+                       iclock_tv.tv_sec = tv->tv_sec;
+                       iclock_tv.tv_usec = tv->tv_usec;
+               } else
+                       do_gettimeofday(&iclock_tv);
+       } else {
+               /* calc elapsed time by system clock */
+               if (tv) { /* tv must be set, if function call is delayed */
+                       tv_now.tv_sec = tv->tv_sec;
+                       tv_now.tv_usec = tv->tv_usec;
+               } else
+                       do_gettimeofday(&tv_now);
+               elapsed_sec = tv_now.tv_sec - iclock_tv.tv_sec;
+               elapsed_8000th = (tv_now.tv_usec / 125)
+                       - (iclock_tv.tv_usec / 125);
+               if (elapsed_8000th < 0) {
+                       elapsed_sec -= 1;
+                       elapsed_8000th += 8000;
+               }
+               /* add elapsed time to counter and set new timestamp */
+               iclock_count += elapsed_sec * 8000 + elapsed_8000th;
+               iclock_tv.tv_sec = tv_now.tv_sec;
+               iclock_tv.tv_usec = tv_now.tv_usec;
+               iclock_tv_valid = 1;
+               if (*debug & DEBUG_CLOCK)
+                       printk("Received first clock from source '%s'.\n",
+                           iclock_current ? iclock_current->name : "nothing");
+       }
+       write_unlock_irqrestore(&iclock_lock, flags);
+}
+EXPORT_SYMBOL(mISDN_clock_update);
+
+unsigned short
+mISDN_clock_get(void)
+{
+       u_long          flags;
+       struct timeval  tv_now;
+       time_t          elapsed_sec;
+       int             elapsed_8000th;
+       u16             count;
+
+       read_lock_irqsave(&iclock_lock, flags);
+       /* calc elapsed time by system clock */
+       do_gettimeofday(&tv_now);
+       elapsed_sec = tv_now.tv_sec - iclock_tv.tv_sec;
+       elapsed_8000th = (tv_now.tv_usec / 125) - (iclock_tv.tv_usec / 125);
+       if (elapsed_8000th < 0) {
+               elapsed_sec -= 1;
+               elapsed_8000th += 8000;
+       }
+       /* add elapsed time to counter */
+       count = iclock_count + elapsed_sec * 8000 + elapsed_8000th;
+       read_unlock_irqrestore(&iclock_lock, flags);
+       return count;
+}
+EXPORT_SYMBOL(mISDN_clock_get);
+
index 43fd97b0fe42bc9a81b98029b4fff8814ad33b83..9116f54def2c19cde23c57348ac797fdae1217b0 100644 (file)
@@ -199,6 +199,7 @@ mISDNInit(void)
 
        printk(KERN_INFO "Modular ISDN core version %d.%d.%d\n",
                MISDN_MAJOR_VERSION, MISDN_MINOR_VERSION, MISDN_RELEASE);
+       mISDN_init_clock(&debug);
        mISDN_initstack(&debug);
        err = mISDN_inittimer(&debug);
        if (err)
index 7da7233b4c1a4f611256e4830798c14cea370c2b..7ac2f81a812b0f32eab886684800ab4c937158d4 100644 (file)
@@ -74,4 +74,6 @@ extern void   l1_cleanup(void);
 extern int     Isdnl2_Init(u_int *);
 extern void    Isdnl2_cleanup(void);
 
+extern void    mISDN_init_clock(u_int *);
+
 #endif
index 04dbb407f7a0e907f74f61b4e3b75972ad44cdf8..efe4c7430e6d8ea016436cba348f17e309cb0e21 100644 (file)
@@ -1557,13 +1557,11 @@ send_packet:
        schedule_work(&dsp->workq);
 }
 
-static u32     samplecount;
+static u32     jittercount; /* counter for jitter check */;
 struct timer_list dsp_spl_tl;
 u32    dsp_spl_jiffies; /* calculate the next time to fire */
-#ifdef UNUSED
-static u32     dsp_start_jiffies; /* jiffies at the time, the calculation begins */
-#endif /* UNUSED */
-static struct timeval dsp_start_tv; /* time at start of calculation */
+static u16     dsp_count; /* last sample count */
+static int     dsp_count_valid ; /* if we have last sample count */
 
 void
 dsp_cmx_send(void *arg)
@@ -1577,38 +1575,32 @@ dsp_cmx_send(void *arg)
        int r, rr;
        int jittercheck = 0, delay, i;
        u_long flags;
-       struct timeval tv;
-       u32 elapsed;
-       s16 length;
+       u16 length, count;
 
        /* lock */
        spin_lock_irqsave(&dsp_lock, flags);
 
-       if (!dsp_start_tv.tv_sec) {
-               do_gettimeofday(&dsp_start_tv);
+       if (!dsp_count_valid) {
+               dsp_count = mISDN_clock_get();
                length = dsp_poll;
+               dsp_count_valid = 1;
        } else {
-               do_gettimeofday(&tv);
-               elapsed = ((tv.tv_sec - dsp_start_tv.tv_sec) * 8000)
-                   + ((s32)(tv.tv_usec / 125) - (dsp_start_tv.tv_usec / 125));
-               dsp_start_tv.tv_sec = tv.tv_sec;
-               dsp_start_tv.tv_usec = tv.tv_usec;
-               length = elapsed;
+               count = mISDN_clock_get();
+               length = count - dsp_count;
+               dsp_count = count;
        }
        if (length > MAX_POLL + 100)
                length = MAX_POLL + 100;
-/* printk(KERN_DEBUG "len=%d dsp_count=0x%x.%04x dsp_poll_diff=0x%x.%04x\n",
- length, dsp_count >> 16, dsp_count & 0xffff, dsp_poll_diff >> 16,
- dsp_poll_diff & 0xffff);
- */
+       /* printk(KERN_DEBUG "len=%d dsp_count=0x%x\n", length, dsp_count); */
 
        /*
-        * check if jitter needs to be checked
-        * (this is about every second = 8192 samples)
+        * check if jitter needs to be checked (this is every second)
         */
-       samplecount += length;
-       if ((samplecount & 8191) < length)
+       jittercount += length;
+       if (jittercount >= 8000) {
+               jittercount -= 8000;
                jittercheck = 1;
+       }
 
        /* loop all members that do not require conference mixing */
        list_for_each_entry(dsp, &dsp_ilist, list) {
index f75d596c5316232d1641a90df33008408d0c1063..364f1018f0d124b8dec8600b16dd2999cd9b5079 100644 (file)
@@ -371,6 +371,7 @@ struct mISDN_ctrl_req {
 #define DEBUG_L2_TEI           0x00100000
 #define DEBUG_L2_TEIFSM                0x00200000
 #define DEBUG_TIMER            0x01000000
+#define DEBUG_CLOCK            0x02000000
 
 #define mISDN_HEAD_P(s)                ((struct mISDNhead *)&s->cb[0])
 #define mISDN_HEAD_PRIM(s)     (((struct mISDNhead *)&s->cb[0])->prim)
@@ -384,6 +385,7 @@ struct mISDN_ctrl_req {
 struct mISDNchannel;
 struct mISDNdevice;
 struct mISDNstack;
+struct mISDNclock;
 
 struct channel_req {
        u_int                   protocol;
@@ -460,6 +462,16 @@ struct mISDNstack {
 #endif
 };
 
+typedef        int     (clockctl_func_t)(void *, int);
+
+struct mISDNclock {
+       struct list_head        list;
+       char                    name[64];
+       int                     pri;
+       clockctl_func_t         *ctl;
+       void                    *priv;
+};
+
 /* global alloc/queue functions */
 
 static inline struct sk_buff *
@@ -510,8 +522,13 @@ extern int mISDN_register_device(struct mISDNdevice *, char *name);
 extern void    mISDN_unregister_device(struct mISDNdevice *);
 extern int     mISDN_register_Bprotocol(struct Bprotocol *);
 extern void    mISDN_unregister_Bprotocol(struct Bprotocol *);
+extern struct mISDNclock *mISDN_register_clock(char *, int, clockctl_func_t *,
+                                               void *);
+extern void    mISDN_unregister_clock(struct mISDNclock *);
 
 extern void    set_channel_address(struct mISDNchannel *, u_int, u_int);
+extern void    mISDN_clock_update(struct mISDNclock *, int, struct timeval *);
+extern unsigned short mISDN_clock_get(void);
 
 #endif /* __KERNEL__ */
 #endif /* mISDNIF_H */