xpd_echo: XPP Octasic echo canceler module
authorOron Peled <oron.peled@xorcom.com>
Tue, 28 Jun 2011 18:23:00 +0000 (18:23 +0000)
committerTzafrir Cohen <tzafrir.cohen@xorcom.com>
Tue, 28 Jun 2011 18:23:00 +0000 (18:23 +0000)
* xpd_echo (card_echo.c) - a module to handle an Astribank hardware echo
  canceller module.
* All other XPDs are now of type 'telephony_device'. Only a telephony device
  XPD provides a span to register.
* The EC module will typically show up as XPD-40 and will always show up as
  Unregistered in 'dahdi_hardware -v'

Signed-off-by: Oron Peled <oron.peled@xorcom.com>
Signed-off-by: Tzafrir Cohen <tzafrir.cohen@xorcom.com>

git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@9993 a0bf4364-ded3-4de4-8d8a-66a801d63aff

18 files changed:
drivers/dahdi/xpp/Kbuild
drivers/dahdi/xpp/card_bri.c
drivers/dahdi/xpp/card_echo.c [new file with mode: 0644]
drivers/dahdi/xpp/card_echo.h [new file with mode: 0644]
drivers/dahdi/xpp/card_fxo.c
drivers/dahdi/xpp/card_fxs.c
drivers/dahdi/xpp/card_pri.c
drivers/dahdi/xpp/xbus-core.c
drivers/dahdi/xpp/xbus-core.h
drivers/dahdi/xpp/xbus-pcm.c
drivers/dahdi/xpp/xbus-pcm.h
drivers/dahdi/xpp/xbus-sysfs.c
drivers/dahdi/xpp/xpd.h
drivers/dahdi/xpp/xpp_dahdi.c
drivers/dahdi/xpp/xpp_dahdi.h
drivers/dahdi/xpp/xpp_debug
drivers/dahdi/xpp/xproto.c
drivers/dahdi/xpp/xproto.h

index 13c23f5..8fa83ea 100644 (file)
@@ -13,6 +13,7 @@ obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_FXS)         += xpd_fxs.o
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_FXO)          += xpd_fxo.o
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_PRI)          += xpd_pri.o
 obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_BRI)          += xpd_bri.o
+obj-$(DAHDI_BUILD_ALL)$(CONFIG_DAHDI_XPD_ECHO)         += xpd_echo.o
 
 # Build only supported modules
 ifneq  (,$(filter y m,$(CONFIG_USB)))
@@ -27,6 +28,7 @@ xpd_fxs-objs          += card_fxs.o
 xpd_fxo-objs           += card_fxo.o
 xpd_bri-objs           += card_bri.o
 xpd_pri-objs           += card_pri.o
+xpd_echo-objs          += card_echo.o
 xpp_mmap-objs          += mmapbus.o mmapdrv.o
 
 ifeq   (y,$(PARPORT_DEBUG))
index f3b5565..86f0534 100644 (file)
@@ -863,6 +863,8 @@ static const struct dahdi_span_ops BRI_span_ops = {
        .hooksig = xpp_hooksig, /* Only with RBS bits */
        .ioctl = xpp_ioctl,
        .maint = xpp_maint,
+       .echocan_create = xpp_echocan_create,
+       .echocan_name = xpp_echocan_name,
 #ifdef DAHDI_SYNC_TICK
        .sync_tick = dahdi_sync_tick,
 #endif
@@ -1428,7 +1430,33 @@ static void BRI_card_pcm_tospan(xpd_t *xpd, xpacket_t *pack)
        }
 }
 
+int BRI_echocancel_timeslot(xpd_t *xpd, int pos)
+{
+       return xpd->addr.subunit * 4 + pos;
+}
+
+static int BRI_echocancel_setmask(xpd_t *xpd, xpp_line_t ec_mask)
+{
+       struct BRI_priv_data    *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(GENERAL, xpd, "0x%8X\n", ec_mask);
+       if (!ECHOOPS(xpd->xbus)) {
+               XPD_DBG(GENERAL, xpd,
+                               "No echo canceller in XBUS: Doing nothing.\n");
+               return -EINVAL;
+       }
+       for (i = 0; i < PHONEDEV(xpd).channels - 1; i++) {
+               int     on = BIT(i) & ec_mask;
 
+               CALL_EC_METHOD(ec_set, xpd->xbus, xpd, i, on);
+       }
+       CALL_EC_METHOD(ec_update, xpd->xbus, xpd->xbus);
+       return 0;
+}
 
 /*---------------- BRI: HOST COMMANDS -------------------------------------*/
 
@@ -1670,6 +1698,8 @@ static const struct phoneops      bri_phoneops = {
        .card_pcm_fromspan      = BRI_card_pcm_fromspan,
        .card_pcm_tospan        = BRI_card_pcm_tospan,
        .card_timing_priority   = generic_timing_priority,
+       .echocancel_timeslot    = BRI_echocancel_timeslot,
+       .echocancel_setmask     = BRI_echocancel_setmask,
        .card_ioctl     = BRI_card_ioctl,
        .card_open      = BRI_card_open,
        .card_close     = BRI_card_close,
diff --git a/drivers/dahdi/xpp/card_echo.c b/drivers/dahdi/xpp/card_echo.c
new file mode 100644 (file)
index 0000000..cdd2ce2
--- /dev/null
@@ -0,0 +1,387 @@
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2011, Xorcom
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include "xpd.h"
+#include "xproto.h"
+#include "card_echo.h"
+#include "xpp_dahdi.h"
+#include "dahdi_debug.h"
+#include "xpd.h"
+#include "xbus-core.h"
+
+static const char rcsid[] = "$Id$";
+
+/* must be before dahdi_debug.h: */
+static DEF_PARM(int, debug, 0, 0644, "Print DBG statements");
+
+/*---------------- ECHO Protocol Commands ----------------------------------*/
+
+static bool echo_packet_is_valid(xpacket_t *pack);
+static void echo_packet_dump(const char *msg, xpacket_t *pack);
+
+DEF_RPACKET_DATA(ECHO, SET,
+       byte timeslots[ECHO_TIMESLOTS];
+       );
+
+DEF_RPACKET_DATA(ECHO, SET_REPLY,
+       byte status;
+       byte reserved;
+       );
+
+struct ECHO_priv_data {
+};
+
+static xproto_table_t  PROTO_TABLE(ECHO);
+
+/*---------------- ECHO: Methods -------------------------------------------*/
+
+static xpd_t *ECHO_card_new(xbus_t *xbus, int unit, int subunit,
+               const xproto_table_t *proto_table, byte subtype,
+               int subunits, int subunit_ports, bool to_phone)
+{
+       xpd_t *xpd = NULL;
+       struct ECHO_priv_data *priv;
+       int channels = 0;
+
+       if (subunit_ports != 1) {
+               XBUS_ERR(xbus, "Bad subunit_ports=%d\n", subunit_ports);
+               return NULL;
+       }
+       XBUS_DBG(GENERAL, xbus, "\n");
+       xpd = xpd_alloc(xbus, unit, subunit, subtype, subunits,
+               sizeof(struct ECHO_priv_data), proto_table, channels);
+       if (!xpd)
+               return NULL;
+       xpd->type_name = "ECHO";
+       priv = xpd->priv;
+       return xpd;
+}
+
+static int ECHO_card_init(xbus_t *xbus, xpd_t *xpd)
+{
+       struct ECHO_priv_data *priv;
+       xproto_table_t *proto_table;
+       int ret = 0;
+
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "\n");
+       xpd->type = XPD_TYPE_ECHO;
+       proto_table = &PROTO_TABLE(ECHO);
+       priv = xpd->priv;
+       XPD_DBG(DEVICES, xpd, "%s\n", xpd->type_name);
+       ret = CALL_EC_METHOD(ec_update, xbus, xbus);
+       return ret;
+}
+
+static int ECHO_card_remove(xbus_t *xbus, xpd_t *xpd)
+{
+       struct ECHO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       XPD_DBG(GENERAL, xpd, "\n");
+       return 0;
+}
+
+static int ECHO_card_tick(xbus_t *xbus, xpd_t *xpd)
+{
+       struct ECHO_priv_data *priv;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       return 0;
+}
+
+static int ECHO_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
+{
+       unsigned long flags;
+       struct xpd_addr addr;
+       xpd_t *orig_xpd;
+       byte regnum;
+       byte data_low;
+
+       /* Map UNIT + PORTNUM to XPD */
+       orig_xpd = xpd;
+       addr.unit = orig_xpd->addr.unit;
+       addr.subunit = info->portnum;
+       regnum = REG_FIELD(info, regnum);
+       data_low = REG_FIELD(info, data_low);
+       xpd = xpd_byaddr(xbus, addr.unit, addr.subunit);
+       if (!xpd) {
+               static int rate_limit;
+
+               if ((rate_limit++ % 1003) < 5)
+                       notify_bad_xpd(__func__, xbus, addr,
+                                       orig_xpd->xpdname);
+               return -EPROTO;
+       }
+       spin_lock_irqsave(&xpd->lock, flags);
+       /* Update /proc info only if reply related to last reg read request */
+       if (
+                       REG_FIELD(&xpd->requested_reply, regnum) ==
+                               REG_FIELD(info, regnum) &&
+                       REG_FIELD(&xpd->requested_reply, do_subreg) ==
+                               REG_FIELD(info, do_subreg) &&
+                       REG_FIELD(&xpd->requested_reply, subreg) ==
+                               REG_FIELD(info, subreg)) {
+               xpd->last_reply = *info;
+       }
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return 0;
+}
+
+/*---------------- ECHO: HOST COMMANDS -------------------------------------*/
+
+static /* 0x39 */ HOSTCMD(ECHO, SET)
+{
+       struct xbus_echo_state *es;
+       byte *ts;
+       xframe_t *xframe;
+       xpacket_t *pack;
+       int ret;
+       uint16_t frm_len;
+       int xpd_idx;
+
+       BUG_ON(!xbus);
+       /*
+        * Find echo canceller XPD address
+        */
+       es = &xbus->echo_state;
+       xpd_idx = es->xpd_idx;
+       XFRAME_NEW_CMD(xframe, pack, xbus, ECHO, SET, xpd_idx);
+       ts = RPACKET_FIELD(pack, ECHO, SET, timeslots);
+       memcpy(ts, es->timeslots, ECHO_TIMESLOTS);
+       frm_len = XFRAME_LEN(xframe);
+       XBUS_DBG(GENERAL, xbus, "ECHO SET: (len = %d)\n", frm_len);
+       ret = send_cmd_frame(xbus, xframe);
+       return ret;
+}
+
+static int ECHO_ec_set(xpd_t *xpd, int pos, bool on)
+{
+       int ts_number;
+       int ts_mask;
+       byte *ts;
+
+       ts = xpd->xbus->echo_state.timeslots;
+       /*
+        * ts_number = PCM time slot ("channel number" in the PCM XPP packet)
+        *
+        * Bit 0 is for UNIT=0
+        * PRI: ts_number * 4 + SUBUNIT
+        * BRI: ts_number
+        * FXS/FXO(all units): UNIT * 32 + ts_number
+        *
+        * Bit 1 is for UNIT=1-3: FXS/FXO
+        *
+        */
+       ts_mask = (xpd->addr.unit == 0) ? 0x1 : 0x2;    /* Which bit? */
+       ts_number = CALL_PHONE_METHOD(echocancel_timeslot, xpd, pos);
+       if (ts_number >= ECHO_TIMESLOTS || ts_number < 0) {
+               XPD_ERR(xpd, "Bad ts_number=%d\n", ts_number);
+               return -EINVAL;
+       } else {
+               if (on)
+                       ts[ts_number] |= ts_mask;
+               else
+                       ts[ts_number] &= ~ts_mask;
+       }
+       LINE_DBG(GENERAL, xpd, pos, "%s = %d -- ts_number=%d ts_mask=0x%X\n",
+               __func__, on, ts_number, ts_mask);
+       return 0;
+}
+
+static int ECHO_ec_get(xpd_t *xpd, int pos)
+{
+       int ts_number;
+       int ts_mask;
+       int is_on;
+       byte *ts;
+
+       ts = xpd->xbus->echo_state.timeslots;
+       ts_mask = (xpd->addr.unit == 0) ? 0x1 : 0x2;    /* Which bit? */
+       ts_number = CALL_PHONE_METHOD(echocancel_timeslot, xpd, pos);
+       if (ts_number >= ECHO_TIMESLOTS || ts_number < 0) {
+               XPD_ERR(xpd, "Bad ts_number=%d\n", ts_number);
+               return -EINVAL;
+       } else {
+               is_on = ts[ts_number] & ts_mask;
+       }
+#if 0
+       LINE_DBG(GENERAL, xpd, pos, "ec_get=%d -- ts_number=%d ts_mask=0x%X\n",
+               is_on, ts_number, ts_mask);
+#endif
+       return is_on;
+}
+
+static void ECHO_ec_dump(xbus_t *xbus)
+{
+       byte *ts;
+       int i;
+
+       ts = xbus->echo_state.timeslots;
+       for (i = 0; i + 15 < ECHO_TIMESLOTS; i += 16) {
+               XBUS_DBG(GENERAL, xbus,
+                       "EC-DUMP[%03d]: "
+                       "0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X "
+                       "0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X 0x%02X\n",
+                       i,
+                       ts[i+0], ts[i+1], ts[i+2], ts[i+3], ts[i+4], ts[i+5],
+                               ts[i+6], ts[i+7],
+                       ts[i+8], ts[i+9], ts[i+10], ts[i+11], ts[i+12],
+                               ts[i+13], ts[i+14], ts[i+15]
+                       );
+       }
+}
+
+static int ECHO_ec_update(xbus_t *xbus)
+{
+       XBUS_DBG(GENERAL, xbus, "%s\n", __func__);
+       //ECHO_ec_dump(xbus);
+       return CALL_PROTO(ECHO, SET, xbus, NULL);
+}
+
+/*---------------- ECHO: Astribank Reply Handlers --------------------------*/
+HANDLER_DEF(ECHO, SET_REPLY)
+{
+       byte    status;
+
+       BUG_ON(!xpd);
+       status = RPACKET_FIELD(pack, ECHO, SET_REPLY, status);
+       XPD_DBG(GENERAL, xpd, "status=0x%X\n", status);
+       return 0;
+}
+
+static const struct xops       echo_xops = {
+       .card_new               = ECHO_card_new,
+       .card_init              = ECHO_card_init,
+       .card_remove            = ECHO_card_remove,
+       .card_tick              = ECHO_card_tick,
+       .card_register_reply    = ECHO_card_register_reply,
+};
+
+static const struct echoops    echoops = {
+       .ec_set         = ECHO_ec_set,
+       .ec_get         = ECHO_ec_get,
+       .ec_update      = ECHO_ec_update,
+       .ec_dump        = ECHO_ec_dump,
+};
+
+static xproto_table_t PROTO_TABLE(ECHO) = {
+       .owner = THIS_MODULE,
+       .entries = {
+               /*      Table   Card    Opcode          */
+               XENTRY(ECHO,    ECHO,   SET_REPLY),
+       },
+       .name = "ECHO",
+       .ports_per_subunit = 1,
+       .type = XPD_TYPE_ECHO,
+       .xops = &echo_xops,
+       .echoops = &echoops,
+       .packet_is_valid = echo_packet_is_valid,
+       .packet_dump = echo_packet_dump,
+};
+
+static bool echo_packet_is_valid(xpacket_t *pack)
+{
+       const xproto_entry_t    *xe = NULL;
+       // DBG(GENERAL, "\n");
+       xe = xproto_card_entry(&PROTO_TABLE(ECHO), XPACKET_OP(pack));
+       return xe != NULL;
+}
+
+static void echo_packet_dump(const char *msg, xpacket_t *pack)
+{
+       DBG(GENERAL, "%s\n", msg);
+}
+
+/*------------------------- sysfs stuff --------------------------------*/
+static int echo_xpd_probe(struct device *dev)
+{
+       xpd_t   *ec_xpd;
+       int     ret = 0;
+
+       ec_xpd = dev_to_xpd(dev);
+       /* Is it our device? */
+       if (ec_xpd->type != XPD_TYPE_ECHO) {
+               XPD_ERR(ec_xpd, "drop suggestion for %s (%d)\n",
+                       dev_name(dev), ec_xpd->type);
+               return -EINVAL;
+       }
+       XPD_DBG(DEVICES, ec_xpd, "SYSFS\n");
+       return ret;
+}
+
+static int echo_xpd_remove(struct device *dev)
+{
+       xpd_t   *ec_xpd;
+
+       ec_xpd = dev_to_xpd(dev);
+       XPD_DBG(DEVICES, ec_xpd, "SYSFS\n");
+       return 0;
+}
+
+static struct xpd_driver       echo_driver = {
+       .type           = XPD_TYPE_ECHO,
+       .driver         = {
+               .name = "echo",
+#ifndef OLD_HOTPLUG_SUPPORT
+               .owner = THIS_MODULE,
+#endif
+               .probe = echo_xpd_probe,
+               .remove = echo_xpd_remove
+       }
+};
+
+static int __init card_echo_startup(void)
+{
+       int ret;
+
+       ret = xpd_driver_register(&echo_driver.driver);
+       if (ret < 0)
+               return ret;
+       INFO("revision %s\n", XPP_VERSION);
+       INFO("FEATURE: WITH Octasic echo canceller\n");
+       xproto_register(&PROTO_TABLE(ECHO));
+       return 0;
+}
+
+static void __exit card_echo_cleanup(void)
+{
+       DBG(GENERAL, "\n");
+       xproto_unregister(&PROTO_TABLE(ECHO));
+       xpd_driver_unregister(&echo_driver.driver);
+}
+
+MODULE_DESCRIPTION("XPP ECHO Card Driver");
+MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(XPP_VERSION);
+MODULE_ALIAS_XPD(XPD_TYPE_ECHO);
+
+module_init(card_echo_startup);
+module_exit(card_echo_cleanup);
diff --git a/drivers/dahdi/xpp/card_echo.h b/drivers/dahdi/xpp/card_echo.h
new file mode 100644 (file)
index 0000000..81fea05
--- /dev/null
@@ -0,0 +1,31 @@
+#ifndef        CARD_ECHO_H
+#define        CARD_ECHO_H
+/*
+ * Written by Oron Peled <oron@actcom.co.il>
+ * Copyright (C) 2011, Xorcom
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+#include "xpd.h"
+
+enum echo_opcodes {
+       XPROTO_NAME(ECHO, SET)          = 0x39,
+       XPROTO_NAME(ECHO, SET_REPLY)    = 0x3A,
+};
+
+#endif /* CARD_ECHO_H */
index f533ac6..673e7d6 100644 (file)
@@ -1118,6 +1118,8 @@ static const struct phoneops      fxo_phoneops = {
        .card_pcm_fromspan      = generic_card_pcm_fromspan,
        .card_pcm_tospan        = generic_card_pcm_tospan,
        .card_timing_priority   = generic_timing_priority,
+       .echocancel_timeslot    = generic_echocancel_timeslot,
+       .echocancel_setmask     = generic_echocancel_setmask,
        .card_ioctl     = FXO_card_ioctl,
        .card_open      = FXO_card_open,
        .card_state     = FXO_card_state,
index 03e3d49..01d4ab7 100644 (file)
@@ -1402,6 +1402,8 @@ static const struct phoneops      fxs_phoneops = {
        .card_pcm_fromspan      = generic_card_pcm_fromspan,
        .card_pcm_tospan        = generic_card_pcm_tospan,
        .card_timing_priority   = generic_timing_priority,
+       .echocancel_timeslot    = generic_echocancel_timeslot,
+       .echocancel_setmask     = generic_echocancel_setmask,
        .card_open      = FXS_card_open,
        .card_close     = FXS_card_close,
        .card_ioctl     = FXS_card_ioctl,
index 8d7e76d..604163a 100644 (file)
@@ -1213,6 +1213,8 @@ static const struct dahdi_span_ops PRI_span_ops = {
        .close = xpp_close,
        .ioctl = xpp_ioctl,
        .maint = xpp_maint,
+       .echocan_create = xpp_echocan_create,
+       .echocan_name = xpp_echocan_name,
 #ifdef DAHDI_SYNC_TICK
        .sync_tick = dahdi_sync_tick,
 #endif
@@ -1793,7 +1795,38 @@ int PRI_timing_priority(xpd_t *xpd)
        return -ENOENT;
 }
 
+static int PRI_echocancel_timeslot(xpd_t *xpd, int pos)
+{
+       /*
+        * Skip ts=0 (used for PRI sync)
+        */
+       return (1 + pos) * 4 + xpd->addr.subunit;
+}
 
+static int PRI_echocancel_setmask(xpd_t *xpd, xpp_line_t ec_mask)
+{
+       struct PRI_priv_data *priv;
+       int i;
+
+       BUG_ON(!xpd);
+       priv = xpd->priv;
+       BUG_ON(!priv);
+       XPD_DBG(GENERAL, xpd, "0x%8X\n", ec_mask);
+       if (!ECHOOPS(xpd->xbus)) {
+               XPD_DBG(GENERAL, xpd,
+                               "No echo canceller in XBUS: Doing nothing.\n");
+               return -EINVAL;
+       }
+       for (i = 0; i < PHONEDEV(xpd).channels; i++) {
+               int     on = BIT(i) & ec_mask;
+
+               if (i == PRI_DCHAN_IDX(priv))
+                       on = 0;
+               CALL_EC_METHOD(ec_set, xpd->xbus, xpd, i, on);
+       }
+       CALL_EC_METHOD(ec_update, xpd->xbus, xpd->xbus);
+       return 0;
+}
 
 /*---------------- PRI: HOST COMMANDS -------------------------------------*/
 
@@ -2111,6 +2144,8 @@ static const struct phoneops      pri_phoneops = {
        .card_pcm_recompute     = PRI_card_pcm_recompute,
        .card_pcm_fromspan      = PRI_card_pcm_fromspan,
        .card_pcm_tospan        = PRI_card_pcm_tospan,
+       .echocancel_timeslot    = PRI_echocancel_timeslot,
+       .echocancel_setmask     = PRI_echocancel_setmask,
        .card_timing_priority   = PRI_timing_priority,
        .card_ioctl     = PRI_card_ioctl,
        .card_close     = PRI_card_close,
index d558a75..3d37f71 100644 (file)
@@ -630,6 +630,7 @@ static int new_card(xbus_t *xbus,
        int                     subunits;
        int                     ret = 0;
        int                     remaining_ports;
+       const struct echoops    *echoops;
 
        proto_table = xproto_get(type);
        if(!proto_table) {
@@ -638,6 +639,18 @@ static int new_card(xbus_t *xbus,
                        unit, type);
                return -EINVAL;
        }
+       echoops = proto_table->echoops;
+       if (echoops) {
+               XBUS_INFO(xbus, "Detected ECHO Canceler (%d)\n", unit);
+               if (ECHOOPS(xbus)) {
+                       XBUS_NOTICE(xbus,
+                               "CARD %d: tryies to define echoops (type %d) but we already have one. Ignored.\n",
+                               unit, type);
+                       return -EINVAL;
+               }
+               xbus->echo_state.echoops = echoops;
+               xbus->echo_state.xpd_idx = XPD_IDX(unit, 0);
+       }
        remaining_ports = ports;
        subunits = (ports + proto_table->ports_per_subunit - 1) /
                        proto_table->ports_per_subunit;
@@ -748,6 +761,11 @@ static int xpd_initialize(xpd_t *xpd)
        }
        xpd->card_present = 1;
        if (IS_PHONEDEV(xpd)) {
+               /*
+                * Set echo canceler channels (off)
+                * Asterisk will tell us when/if it's needed.
+                */
+               CALL_PHONE_METHOD(echocancel_setmask, xpd, 0);
                CALL_PHONE_METHOD(card_state, xpd, 1);  /* Turn on all channels */
        }
        if(!xpd_setstate(xpd, XPD_STATE_READY)) {
@@ -760,6 +778,34 @@ out:
        return ret;
 }
 
+static int xbus_echocancel(xbus_t *xbus, int on)
+{
+       int unit;
+       int subunit;
+       xpd_t *xpd;
+
+       if (!ECHOOPS(xbus))
+               return 0;
+       for (unit = 0; unit < MAX_UNIT; unit++) {
+               xpd = xpd_byaddr(xbus, unit, 0);
+               if (!xpd || !IS_PHONEDEV(xpd))
+                       continue;
+               for (subunit = 0; subunit < MAX_SUBUNIT; subunit++) {
+                       int     ret;
+
+                       xpd = xpd_byaddr(xbus, unit, subunit);
+                       if (!xpd || !IS_PHONEDEV(xpd))
+                               continue;
+                       ret = echocancel_xpd(xpd, on);
+                       if (ret < 0) {
+                               XPD_ERR(xpd, "Fail in xbus_echocancel()\n");
+                               return ret;
+                       }
+               }
+       }
+       return 0;
+}
+
 static int xbus_initialize(xbus_t *xbus)
 {
        int     unit;
@@ -807,6 +853,7 @@ static int xbus_initialize(xbus_t *xbus)
                                goto err;
                }
        }
+       xbus_echocancel(xbus, 1);
        do_gettimeofday(&time_end);
        timediff = usec_diff(&time_end, &time_start);
        timediff /= 1000*100;
@@ -1154,6 +1201,7 @@ void xbus_deactivate(xbus_t *xbus)
                return;
        xbus_request_sync(xbus, SYNC_MODE_NONE);        /* no more ticks */
        elect_syncer("deactivate");
+       xbus_echocancel(xbus, 0);
        xbus_request_removal(xbus);
        XBUS_DBG(DEVICES, xbus, "[%s] Waiting for queues\n", xbus->label);
        xbus_command_queue_clean(xbus);
index 659d3d4..b781e94 100644 (file)
@@ -156,6 +156,28 @@ void       put_xbus(const char *msg, xbus_t *xbus);
 int    refcount_xbus(xbus_t *xbus);
 
 /*
+ * Echo canceller related data
+ */
+#define        ECHO_TIMESLOTS  128
+
+struct echoops {
+       int     (*ec_set)(xpd_t *xpd, int pos, bool on);
+       int     (*ec_get)(xpd_t *xpd, int pos);
+       int     (*ec_update)(xbus_t *xbus);
+       void    (*ec_dump)(xbus_t *xbus);
+};
+
+struct xbus_echo_state {
+       const struct echoops    *echoops;
+       byte                    timeslots[ECHO_TIMESLOTS];
+       int                     xpd_idx;
+       struct device_attribute *da[MAX_XPDS];
+};
+#define        ECHOOPS(xbus)                   ((xbus)->echo_state.echoops)
+#define        EC_METHOD(name, xbus)           (ECHOOPS(xbus)->name)
+#define        CALL_EC_METHOD(name, xbus, ...) (EC_METHOD(name, (xbus))(__VA_ARGS__))
+
+/*
  * An xbus is a transport layer for Xorcom Protocol commands
  */
 struct xbus {
@@ -169,6 +191,7 @@ struct xbus {
 
        int                     num;
        struct xpd              *xpds[MAX_XPDS];
+       struct xbus_echo_state  echo_state;
 
        int                     command_tick_counter;
        int                     usec_nosend;            /* Firmware flow control */
@@ -311,6 +334,7 @@ int xbus_xpd_unbind(xbus_t *xbus, xpd_t *xpd);
 /* sysfs */
 int    xpd_device_register(xbus_t *xbus, xpd_t *xpd);
 void   xpd_device_unregister(xpd_t *xpd);
+int    echocancel_xpd(xpd_t *xpd, int on);
 
 int    xpp_driver_init(void);
 void   xpp_driver_exit(void);
index 448cf5a..a93de5e 100644 (file)
@@ -935,6 +935,32 @@ out:
        spin_unlock_irqrestore(&xpd->lock, flags);
 }
 
+int generic_echocancel_timeslot(xpd_t *xpd, int pos)
+{
+       return xpd->addr.unit * 32 + pos;
+}
+EXPORT_SYMBOL(generic_echocancel_timeslot);
+
+int generic_echocancel_setmask(xpd_t *xpd, xpp_line_t ec_mask)
+{
+       int i;
+
+       BUG_ON(!xpd);
+       XPD_DBG(GENERAL, xpd, "0x%8X\n", ec_mask);
+       if (!ECHOOPS(xpd->xbus)) {
+               XPD_DBG(GENERAL, xpd,
+                               "No echo canceller in XBUS: Doing nothing.\n");
+               return -EINVAL;
+       }
+       for (i = 0; i < PHONEDEV(xpd).channels; i++) {
+               int on = BIT(i) & ec_mask;
+               CALL_EC_METHOD(ec_set, xpd->xbus, xpd, i, on);
+       }
+       CALL_EC_METHOD(ec_update, xpd->xbus, xpd->xbus);
+       return 0;
+}
+EXPORT_SYMBOL(generic_echocancel_setmask);
+
 static int copy_pcm_tospan(xbus_t *xbus, xframe_t *xframe)
 {
        byte            *xframe_end;
index f050541..4e00b80 100644 (file)
@@ -109,6 +109,8 @@ void                generic_card_pcm_recompute(xpd_t *xpd, xpp_line_t pcm_mask);
 void           generic_card_pcm_fromspan(xpd_t *xpd, xpacket_t *pack);
 void           generic_card_pcm_tospan(xpd_t *xpd, xpacket_t *pack);
 int            generic_timing_priority(xpd_t *xpd);
+int            generic_echocancel_timeslot(xpd_t *xpd, int pos);
+int            generic_echocancel_setmask(xpd_t *xpd, xpp_line_t ec_mask);
 void           fill_beep(u_char *buf, int num, int duration);
 const char     *sync_mode_name(enum sync_mode mode);
 void           xbus_set_command_timer(xbus_t *xbus, bool on);
index d983f17..daf16fc 100644 (file)
@@ -807,6 +807,103 @@ void xpd_device_unregister(xpd_t *xpd)
        dev_set_drvdata(dev, NULL);
 }
 
+static DEVICE_ATTR_READER(echocancel_show, dev, buf)
+{
+       xpd_t *xpd;
+       unsigned long flags;
+       int len = 0;
+       xpp_line_t ec_mask = 0;
+       int i;
+       int ret;
+
+       BUG_ON(!dev);
+       xpd = dev_to_xpd(dev);
+       if (!xpd)
+               return -ENODEV;
+       if (!ECHOOPS(xpd->xbus))
+               return -ENODEV;
+       spin_lock_irqsave(&xpd->lock, flags);
+       for (i = 0; i < PHONEDEV(xpd).channels; i++) {
+               ret = CALL_EC_METHOD(ec_get, xpd->xbus, xpd, i);
+               if (ret < 0) {
+                       LINE_ERR(xpd, i, "ec_get failed\n");
+                       len = -ENODEV;
+                       goto out;
+               }
+               if (ret)
+                       ec_mask |= (1 << i);
+       }
+       len += sprintf(buf, "0x%08X\n", ec_mask);
+out:
+       spin_unlock_irqrestore(&xpd->lock, flags);
+       return len;
+}
+
+static DEVICE_ATTR_WRITER(echocancel_store, dev, buf, count)
+{
+       xpd_t *xpd;
+       char *endp;
+       unsigned long mask;
+       int channels;
+       int ret;
+
+       BUG_ON(!dev);
+       xpd = dev_to_xpd(dev);
+       XPD_DBG(GENERAL, xpd, "%s\n", buf);
+       if (!xpd)
+               return -ENODEV;
+       if (!ECHOOPS(xpd->xbus)) {
+               XPD_ERR(xpd, "No echo canceller in this XBUS\n");
+               return -ENODEV;
+       }
+       if (!IS_PHONEDEV(xpd)) {
+               XPD_ERR(xpd, "Not a phone device\n");
+               return -ENODEV;
+       }
+       channels = PHONEDEV(xpd).channels;
+       mask = simple_strtoul(buf, &endp, 0);
+       if (*endp != '\0' && *endp != '\n' && *endp != '\r') {
+               XPD_ERR(xpd, "Too many channels: %d\n", channels);
+               return -EINVAL;
+       }
+       if (mask != 0 && __ffs(mask) > channels) {
+               XPD_ERR(xpd,
+                       "Channel mask (0x%lX) larger than available channels (%d)\n",
+                       mask, channels);
+               return -EINVAL;
+       }
+       XPD_DBG(GENERAL, xpd, "ECHOCANCEL channels: 0x%lX\n", mask);
+       ret = CALL_PHONE_METHOD(echocancel_setmask, xpd, mask);
+       if (ret < 0) {
+               XPD_ERR(xpd, "echocancel_setmask failed\n");
+               return ret;
+       }
+       return count;
+}
+
+static DEVICE_ATTR(echocancel, S_IRUGO | S_IWUSR, echocancel_show,
+               echocancel_store);
+
+int echocancel_xpd(xpd_t *xpd, int on)
+{
+       int ret;
+
+       XPD_DBG(GENERAL, xpd, "echocancel_xpd(%s)\n", (on) ? "on" : "off");
+       if (!on) {
+               device_remove_file(xpd->echocancel, &dev_attr_echocancel);
+               return 0;
+       }
+
+       ret = device_create_file(&xpd->xpd_dev, &dev_attr_echocancel);
+       if (ret)
+               XPD_ERR(xpd,
+                       "%s: device_create_file(echocancel) failed: %d\n",
+                       __func__, ret);
+
+       return ret;
+}
+EXPORT_SYMBOL(echocancel_xpd);
+
 /*--------- Sysfs Device handling ----*/
 
 void xbus_sysfs_transport_remove(xbus_t *xbus)
index dadbf55..8eaa8dd 100644 (file)
@@ -149,6 +149,8 @@ struct phonedev {
        struct dahdi_span       span;
        struct dahdi_chan       *chans[32];
 #define        XPD_CHAN(xpd,chan)      (PHONEDEV(xpd).chans[(chan)])
+       struct dahdi_echocan_state *ec[32];
+
        int             channels;
        xpd_direction_t direction;              /* TO_PHONE, TO_PSTN */
        xpp_line_t      no_pcm;                 /* Temporary: disable PCM (for USB-1) */
@@ -198,6 +200,7 @@ struct xpd {
 #define kref_to_xpd(k) container_of(k, struct xpd, kref)
 
        xbus_t *xbus;                   /* The XBUS we are connected to */
+       struct device   *echocancel;
 
        spinlock_t      lock;
 
index 89adf51..3bad387 100644 (file)
@@ -463,6 +463,8 @@ static void phonedev_cleanup(xpd_t *xpd)
                if (phonedev->chans[x]) {
                        KZFREE(phonedev->chans[x]);
                }
+               if (phonedev->ec[x])
+                       KZFREE(phonedev->ec[x]);
        }
 }
 
@@ -486,6 +488,13 @@ __must_check static int phonedev_init(xpd_t *xpd, const xproto_table_t *proto_ta
                        ERR("%s: Unable to allocate channel %d\n", __FUNCTION__, x);
                        goto err;
                }
+               phonedev->ec[x] = KZALLOC(sizeof(*(phonedev->ec[x])),
+                               GFP_KERNEL);
+               if (!phonedev->ec[x]) {
+                       ERR("%s: Unable to allocate ec state %d\n", __func__,
+                                       x);
+                       goto err;
+               }
        }
        return 0;
 err:
@@ -887,6 +896,86 @@ static int xpp_watchdog(struct dahdi_span *span, int cause)
 }
 #endif
 
+/*
+ * Hardware Echo Canceller management
+ */
+static void echocan_free(struct dahdi_chan *chan,
+               struct dahdi_echocan_state *ec)
+{
+       xpd_t                   *xpd;
+       xbus_t                  *xbus;
+       int                     pos = chan->chanpos - 1;
+       const struct echoops    *echoops;
+
+       xpd = chan->pvt;
+       xbus = xpd->xbus;
+       echoops = ECHOOPS(xbus);
+       if (!echoops)
+               return;
+       LINE_NOTICE(xpd, pos, "%s: mode=0x%X\n", __func__, ec->status.mode);
+       CALL_EC_METHOD(ec_set, xbus, xpd, pos, 0);
+       CALL_EC_METHOD(ec_update, xbus, xbus);
+}
+
+static const struct dahdi_echocan_features xpp_ec_features = {
+};
+
+static const struct dahdi_echocan_ops xpp_ec_ops = {
+       .echocan_free = echocan_free,
+};
+
+const char *xpp_echocan_name(const struct dahdi_chan *chan)
+{
+       xpd_t   *xpd;
+       xbus_t  *xbus;
+       int     pos;
+
+       if (!chan) {
+               NOTICE("%s(NULL)\n", __func__);
+               return "XPP";
+       }
+       xpd = chan->pvt;
+       xbus = xpd->xbus;
+       pos = chan->chanpos - 1;
+       LINE_DBG(GENERAL, xpd, pos, "%s:\n", __func__);
+       if (!ECHOOPS(xbus))
+               return NULL;
+       return "XPP";
+}
+EXPORT_SYMBOL(xpp_echocan_name);
+
+int xpp_echocan_create(struct dahdi_chan *chan,
+                               struct dahdi_echocanparams *ecp,
+                               struct dahdi_echocanparam *p,
+                               struct dahdi_echocan_state **ec)
+{
+       xpd_t                   *xpd;
+       xbus_t                  *xbus;
+       int                     pos;
+       struct phonedev         *phonedev;
+       const struct echoops    *echoops;
+       int                     ret;
+
+       xpd = chan->pvt;
+       xbus = xpd->xbus;
+       pos = chan->chanpos - 1;
+       echoops = ECHOOPS(xbus);
+       if (!echoops)
+               return -ENODEV;
+       phonedev = &PHONEDEV(xpd);
+       *ec = phonedev->ec[pos];
+       (*ec)->ops = &xpp_ec_ops;
+       (*ec)->features = xpp_ec_features;
+       LINE_NOTICE(xpd, pos, "%s: (tap=%d, param_count=%d)\n",
+               __func__,
+               ecp->tap_length, ecp->param_count);
+       ret = CALL_EC_METHOD(ec_set, xbus, xpd, pos, 1);
+       CALL_EC_METHOD(ec_update, xbus, xbus);
+       return ret;
+}
+EXPORT_SYMBOL(xpp_echocan_create);
+
+
 /**
  * Unregister an xpd from dahdi and release related resources
  * @xpd The xpd to be unregistered
@@ -940,6 +1029,8 @@ static const struct dahdi_span_ops xpp_span_ops = {
        .close = xpp_close,
        .ioctl = xpp_ioctl,
        .maint = xpp_maint,
+       .echocan_create = xpp_echocan_create,
+       .echocan_name = xpp_echocan_name,
 };
 
 static const struct dahdi_span_ops xpp_rbs_span_ops = {
@@ -949,6 +1040,8 @@ static const struct dahdi_span_ops xpp_rbs_span_ops = {
        .close = xpp_close,
        .ioctl = xpp_ioctl,
        .maint = xpp_maint,
+       .echocan_create = xpp_echocan_create,
+       .echocan_name = xpp_echocan_name,
 };
 
 int dahdi_register_xpd(xpd_t *xpd)
index 3ab0299..01f4a24 100644 (file)
@@ -35,6 +35,11 @@ xpd_t *xpd_alloc(xbus_t *xbus, int unit, int subunit, int subtype, int subunits,
 void xpd_free(xpd_t *xpd);
 void xpd_remove(xpd_t *xpd);
 void update_xpd_status(xpd_t *xpd, int alarm_flag);
+const char *xpp_echocan_name(const struct dahdi_chan *chan);
+int xpp_echocan_create(struct dahdi_chan *chan,
+                               struct dahdi_echocanparams *ecp,
+                               struct dahdi_echocanparam *p,
+                               struct dahdi_echocan_state **ec);
 void hookstate_changed(xpd_t *xpd, int pos, bool good);
 int xpp_open(struct dahdi_chan *chan);
 int xpp_close(struct dahdi_chan *chan);
index e7fc0da..a89e0c4 100755 (executable)
@@ -3,7 +3,7 @@
 # xpp_debug: Turn on/off debugging flags via /sys/module/*/parameters/debug
 #
 
-modules="xpp xpp_usb xpd_fxs xpd_fxo xpd_bri xpd_pri"
+modules="xpp xpp_usb xpd_fxs xpd_fxo xpd_bri xpd_pri xpd_echo"
 dbg_names="DEFAULT PCM LEDS SYNC SIGNAL PROC REGS DEVICES COMMANDS"
 
 usage() {
index 570fd85..c5c263a 100644 (file)
@@ -455,6 +455,8 @@ int xproto_register(const xproto_table_t *proto_table)
                CHECK_PHONEOP(phoneops, card_pcm_recompute);
                CHECK_PHONEOP(phoneops, card_pcm_fromspan);
                CHECK_PHONEOP(phoneops, card_pcm_tospan);
+               CHECK_PHONEOP(phoneops, echocancel_timeslot);
+               CHECK_PHONEOP(phoneops, echocancel_setmask);
                CHECK_PHONEOP(phoneops, card_dahdi_preregistration);
                CHECK_PHONEOP(phoneops, card_dahdi_postregistration);
                /* optional method -- call after testing: */
index 78ef980..2d5f88c 100644 (file)
@@ -78,6 +78,7 @@ struct xpacket_header {
 #define        XPD_TYPE_FXO            2       // TO_PSTN
 #define        XPD_TYPE_BRI            3       // TO_PSTN/TO_PHONE (from hardware)
 #define        XPD_TYPE_PRI            4       // TO_PSTN/TO_PHONE (runtime)
+#define        XPD_TYPE_ECHO           5       // Octasic echo canceller
 #define        XPD_TYPE_NOMODULE       7
 
 typedef        byte    xpd_type_t;
@@ -233,6 +234,8 @@ struct phoneops {
        void (*card_pcm_recompute)(xpd_t *xpd, xpp_line_t pcm_mask);
        void (*card_pcm_fromspan)(xpd_t *xpd, xpacket_t *pack);
        void (*card_pcm_tospan)(xpd_t *xpd, xpacket_t *pack);
+       int (*echocancel_timeslot)(xpd_t *xpd, int pos);
+       int (*echocancel_setmask)(xpd_t *xpd, xpp_line_t ec_mask);
        int (*card_timing_priority)(xpd_t *xpd);
        int (*card_dahdi_preregistration)(xpd_t *xpd, bool on);
        int (*card_dahdi_postregistration)(xpd_t *xpd, bool on);
@@ -265,6 +268,7 @@ struct xproto_table {
        xproto_entry_t          entries[256];   /* Indexed by opcode */
        const struct xops       *xops;          /* Card level operations */
        const struct phoneops   *phoneops;      /* DAHDI operations */
+       const struct echoops    *echoops;       /* Echo Canceller operations */
        xpd_type_t      type;
        byte            ports_per_subunit;
        const char      *name;