/* * Written by Oron Peled * 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 #include #include #include #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; 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"; return xpd; } static int ECHO_card_init(xbus_t *xbus, xpd_t *xpd) { int ret = 0; BUG_ON(!xpd); XPD_DBG(GENERAL, xpd, "\n"); xpd->type = XPD_TYPE_ECHO; 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) { BUG_ON(!xpd); 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; /* Map UNIT + PORTNUM to XPD */ orig_xpd = xpd; addr.unit = orig_xpd->addr.unit; addr.subunit = info->portnum; 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 "); MODULE_LICENSE("GPL"); MODULE_VERSION(XPP_VERSION); MODULE_ALIAS_XPD(XPD_TYPE_ECHO); module_init(card_echo_startup); module_exit(card_echo_cleanup);