/* * Written by Oron Peled * Copyright (C) 2004-2006, 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 /* For zaptel version */ #include "xpd.h" #include "xproto.h" #include "xpp_zap.h" #include "card_fxo.h" #include "zap_debug.h" static const char rcsid[] = "$Id$"; DEF_PARM(int, print_dbg, 0, "Print DBG statements"); /* must be before zap_debug.h */ DEF_PARM(uint, poll_battery_interval, 100, "Poll battery interval in milliseconds"); DEF_PARM(bool, report_battery, 0, "Report battery status to zaptel"); /* Signaling is opposite (fxs signalling for fxo card) */ #if 1 #define FXO_DEFAULT_SIGCAP (ZT_SIG_FXSKS | ZT_SIG_FXSLS) #else #define FXO_DEFAULT_SIGCAP (ZT_SIG_SF) #endif enum fxo_leds { LED_GREEN, }; #define NUM_LEDS 1 #define DELAY_UNTIL_DIALTONE 3000 #define BAT_THRESHOLD 3 #define BAT_DEBOUNCE 3 /* compensate for battery voltage fluctuation (in poll_battery_interval's) */ /*---------------- FXO Protocol Commands ----------------------------------*/ static /* 0x0F */ DECLARE_CMD(FXO, CHAN_ENABLE, xpp_line_t lines, bool on); static /* 0x0F */ DECLARE_CMD(FXO, CHAN_CID, int pos); static /* 0x0F */ DECLARE_CMD(FXO, RING, int pos, bool on); static /* 0x0F */ DECLARE_CMD(FXO, RELAY_OUT, byte which, bool on); static /* 0x0F */ DECLARE_CMD(FXO, DAA_QUERY, int pos, byte reg_num); static bool fxo_packet_is_valid(xpacket_t *pack); static void fxo_packet_dump(xpacket_t *pack); static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_xpd_slic_write(struct file *file, const char __user *buffer, unsigned long count, void *data); static int process_slic_cmdline(xpd_t *xpd, char *cmdline); #define PROC_DAA_FNAME "slics" #define PROC_FXO_INFO_FNAME "fxo_info" #ifdef SOFT_RING #define POLL_RING_INTERVAL 2 #define RING_THRESHOLD 3 #define NORING_THRESHOLD 10 #endif #define DAA_RING_REGISTER 0x05 struct FXO_priv_data { struct proc_dir_entry *xpd_slic; struct proc_dir_entry *fxo_info; uint poll_counter; slic_reply_t requested_reply; slic_reply_t last_reply; xpp_line_t battery; ushort battery_debounce[CHANNELS_PERXPD]; xpp_line_t ledstate[NUM_LEDS]; /* 0 - OFF, 1 - ON */ xpp_line_t ledcontrol[NUM_LEDS]; /* 0 - OFF, 1 - ON */ int blinking[NUM_LEDS][CHANNELS_PERXPD]; #ifdef SOFT_RING ushort ring_thresh[CHANNELS_PERXPD]; ushort noring_thresh[CHANNELS_PERXPD]; /* ring_sig is set when Reg5, bit 2 (Ring Detect) is set. * While ring_sig=1 we check R5 bit 20H and 40H for ringing. * When it drops to 0 that's the end of the ring sequence and * we clear the ring detection variables */ ushort ring_sig[CHANNELS_PERXPD]; #endif }; /*---------------- FXO: Static functions ----------------------------------*/ #define IS_BLINKING(priv,pos,color) ((priv)->blinking[color][pos] != 0) #define MARK_BLINK(priv,pos,color,val) ((priv)->blinking[color][pos] = (val)) void MARK_LED(xpd_t *xpd, lineno_t pos, byte color, bool on) { struct FXO_priv_data *priv = xpd->priv; if(on) BIT_SET(priv->ledcontrol[color], pos); else BIT_CLR(priv->ledcontrol[color], pos); } /* * LED control is done via DAA register 0x20 */ static int do_led(xpd_t *xpd, lineno_t pos, byte which, bool on) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; int len; struct FXO_priv_data *priv; xpp_line_t lines; xbus_t *xbus; BUG_ON(!xpd); xbus = xpd->xbus; priv = xpd->priv; which = which % NUM_LEDS; if(IS_SET(xpd->digital_outputs, pos) || IS_SET(xpd->digital_inputs, pos)) goto out; if(pos == ALL_LINES) { lines = ~0; priv->ledstate[which] = (on) ? ~0 : 0; } else { lines = BIT(pos); if(on) { BIT_SET(priv->ledstate[which], pos); } else { BIT_CLR(priv->ledstate[which], pos); } } if(!lines) // Nothing to do goto out; DBG("%s/%s: LED: lines=0x%04X which=%d -- %s\n", xbus->busname, xpd->xpdname, lines, which, (on) ? "on" : "off"); XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd); len = slic_cmd_direct_write(sc, lines, 0x20, on); // DBG("LED pack: line=%d %s\n", i, (on)?"on":"off"); pack->datalen = len; packet_send(xbus, pack); out: return ret; } static void handle_fxo_leds(xpd_t *xpd) { int i; unsigned long flags; const enum fxo_leds color = LED_GREEN; unsigned int timer_count; struct FXO_priv_data *priv; BUG_ON(!xpd); spin_lock_irqsave(&xpd->lock, flags); priv = xpd->priv; timer_count = xpd->timer_count; for_each_line(xpd, i) { if(IS_SET(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i)) continue; if(IS_BLINKING(priv,i,color)) { // led state is toggled if((timer_count % LED_BLINK_PERIOD) == 0) { DBG("%s/%s/%d: ledstate=%s\n", xpd->xbus->busname, xpd->xpdname, i, (IS_SET(priv->ledstate[color], i))?"ON":"OFF"); if(!IS_SET(priv->ledstate[color], i)) { do_led(xpd, i, color, 1); } else { do_led(xpd, i, color, 0); } } } else if(IS_SET(priv->ledcontrol[color], i) && !IS_SET(priv->ledstate[color], i)) { do_led(xpd, i, color, 1); } else if(!IS_SET(priv->ledcontrol[color], i) && IS_SET(priv->ledstate[color], i)) { do_led(xpd, i, color, 0); } } spin_unlock_irqrestore(&xpd->lock, flags); } static void mark_ring(xpd_t *xpd, lineno_t pos, bool on) { struct FXO_priv_data *priv; priv = xpd->priv; BUG_ON(!priv); if(on && !xpd->ringing[pos]) { DBG("%s/%s/%d: START\n", xpd->xbus->busname, xpd->xpdname, pos); xpd->ringing[pos] = 1; MARK_BLINK(priv, pos, LED_GREEN, LED_BLINK); update_zap_ring(xpd, pos, 1); } else if(!on && xpd->ringing[pos]) { DBG("%s/%s/%d: STOP\n", xpd->xbus->busname, xpd->xpdname, pos); xpd->ringing[pos] = 0; if(IS_BLINKING(priv, pos, LED_GREEN)) MARK_BLINK(priv, pos, LED_GREEN, 0); update_zap_ring(xpd, pos, 0); } } static int do_sethook(xpd_t *xpd, int pos, bool to_offhook) { unsigned long flags; xbus_t *xbus; struct FXO_priv_data *priv; int ret = 0; bool value; xpacket_t *pack; slic_cmd_t *sc; int len; BUG_ON(!xpd); BUG_ON(xpd->direction == TO_PHONE); // We can SETHOOK state only on PSTN xbus = xpd->xbus; priv = xpd->priv; BUG_ON(!priv); if(!IS_SET(priv->battery, pos)) { NOTICE("%s/%s/%d: WARNING: called while battery is off\n", xbus->busname, xpd->xpdname, pos); } spin_lock_irqsave(&xpd->lock, flags); mark_ring(xpd, pos, 0); // No more rings value = (to_offhook) ? 0x09 : 0x08; /* Bit 3 is for CID */ DBG("%s/%s/%d: SETHOOK: value=0x%02X %s\n", xbus->busname, xpd->xpdname, pos, value, (to_offhook)?"OFFHOOK":"ONHOOK"); MARK_LED(xpd, pos, LED_GREEN, (to_offhook)?LED_ON:LED_OFF); XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd); len = slic_cmd_direct_write(sc, BIT(pos), DAA_RING_REGISTER, value); pack->datalen = len; packet_send(xbus, pack); #ifdef SOFT_RING priv->ring_sig[pos] = 0; #endif if(to_offhook) { BIT_SET(xpd->offhook, pos); } else { BIT_CLR(xpd->offhook, pos); BIT_CLR(xpd->cid_on, pos); xpd->delay_until_dialtone[pos] = 0; } spin_unlock_irqrestore(&xpd->lock, flags); if(to_offhook) wake_up_interruptible(&xpd->txstateq[pos]); return ret; } /*---------------- FXO: Methods -------------------------------------------*/ static xpd_t *FXO_card_new(xbus_t *xbus, int xpd_num, const xproto_table_t *proto_table, byte revision) { xpd_t *xpd = NULL; int channels = min(8, CHANNELS_PERXPD); xpd = xpd_alloc(sizeof(struct FXO_priv_data), xbus, xpd_num, proto_table, channels, revision); if(!xpd) return NULL; xpd->direction = TO_PSTN; xpd->revision = revision; return xpd; } static void clean_proc(xbus_t *xbus, xpd_t *xpd) { struct FXO_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; DBG("%s/%s\n", xbus->busname, xpd->xpdname); #ifdef CONFIG_PROC_FS if(priv->xpd_slic) { DBG("Removing xpd DAA file %s/%s\n", xbus->busname, xpd->xpdname); remove_proc_entry(PROC_DAA_FNAME, xpd->proc_xpd_dir); priv->xpd_slic = NULL; } if(priv->fxo_info) { DBG("Removing xpd FXO_INFO file %s/%s\n", xbus->busname, xpd->xpdname); remove_proc_entry(PROC_FXO_INFO_FNAME, xpd->proc_xpd_dir); priv->fxo_info = NULL; } #endif } static int FXO_card_init(xbus_t *xbus, xpd_t *xpd) { struct FXO_priv_data *priv; int ret = 0; int i; BUG_ON(!xpd); priv = xpd->priv; #ifdef CONFIG_PROC_FS DBG("Creating FXO_INFO file for %s/%s\n", xbus->busname, xpd->xpdname); priv->fxo_info = create_proc_read_entry(PROC_FXO_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_fxo_info_read, xpd); if(!priv->fxo_info) { ERR("Failed to create proc '%s' for %s/%s\n", PROC_FXO_INFO_FNAME, xbus->busname, xpd->xpdname); ret = -ENOENT; goto err; } priv->fxo_info->owner = THIS_MODULE; DBG("Creating DAAs file for %s/%s\n", xbus->busname, xpd->xpdname); priv->xpd_slic = create_proc_entry(PROC_DAA_FNAME, 0644, xpd->proc_xpd_dir); if(!priv->xpd_slic) { ERR("Failed to create proc file for DAAs of %s/%s\n", xbus->busname, xpd->xpdname); ret = -ENOENT; goto err; } priv->xpd_slic->owner = THIS_MODULE; priv->xpd_slic->write_proc = proc_xpd_slic_write; priv->xpd_slic->read_proc = proc_xpd_slic_read; priv->xpd_slic->data = xpd; #endif ret = run_initialize_registers(xpd); if(ret < 0) goto err; // Hanghup all lines for_each_line(xpd, i) { init_waitqueue_head(&xpd->txstateq[i]); do_sethook(xpd, i, 0); } DBG("done: %s/%s\n", xbus->busname, xpd->xpdname); return 0; err: clean_proc(xbus, xpd); ERR("%s/%s: Failed initializing registers (%d)\n", xbus->busname, xpd->xpdname, ret); return ret; } static int FXO_card_remove(xbus_t *xbus, xpd_t *xpd) { struct FXO_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; DBG("%s/%s\n", xbus->busname, xpd->xpdname); clean_proc(xbus, xpd); return 0; } static int FXO_card_zaptel_preregistration(xpd_t *xpd, bool on) { xbus_t *xbus; struct FXO_priv_data *priv; int i; BUG_ON(!xpd); xbus = xpd->xbus; BUG_ON(!xbus); priv = xpd->priv; BUG_ON(!priv); DBG("%s/%s (%d)\n", xbus->busname, xpd->xpdname, on); snprintf(xpd->span.desc, MAX_SPANDESC, "Xorcom XPD #%d/%d: FXO", xbus->num, xpd->id); for_each_line(xpd, i) { struct zt_chan *cur_chan = &xpd->chans[i]; DBG("setting FXO channel %d\n", i); snprintf(cur_chan->name, MAX_CHANNAME, "XPP_FXO/%d/%d/%d", xbus->num, xpd->id, i); cur_chan->chanpos = i + 1; cur_chan->pvt = xpd; cur_chan->sigcap = FXO_DEFAULT_SIGCAP; } MARK_LED(xpd, ALL_LINES, LED_GREEN, LED_OFF); for_each_line(xpd, i) { MARK_LED(xpd, i, LED_GREEN, LED_ON); mdelay(50); } return 0; } static int FXO_card_zaptel_postregistration(xpd_t *xpd, bool on) { xbus_t *xbus; struct FXO_priv_data *priv; int i; BUG_ON(!xpd); xbus = xpd->xbus; BUG_ON(!xbus); priv = xpd->priv; BUG_ON(!priv); DBG("%s/%s (%d)\n", xbus->busname, xpd->xpdname, on); for_each_line(xpd, i) { MARK_LED(xpd, i, LED_GREEN, LED_OFF); mdelay(50); } return 0; } int FXO_card_hooksig(xbus_t *xbus, xpd_t *xpd, int pos, zt_txsig_t txsig) { struct FXO_priv_data *priv; priv = xpd->priv; BUG_ON(!priv); DBG("%s/%s/%d: %s\n", xbus->busname, xpd->xpdname, pos, txsig2str(txsig)); BUG_ON(xpd->direction != TO_PSTN); /* XXX Enable hooksig for FXO XXX */ switch(txsig) { case ZT_TXSIG_START: break; case ZT_TXSIG_OFFHOOK: do_sethook(xpd, pos, 1); break; case ZT_TXSIG_ONHOOK: do_sethook(xpd, pos, 0); break; default: NOTICE("Can't set tx state to %s (%d)\n", txsig2str(txsig), txsig); return -EINVAL; } return 0; } static void poll_battery(xbus_t *xbus, xpd_t *xpd) { int i; for_each_line(xpd, i) { CALL_PROTO(FXO, DAA_QUERY, xbus, xpd, i, DAA_VBAT_REGISTER); } } #ifdef SOFT_RING static void poll_ring(xbus_t *xbus, xpd_t *xpd) { int i; struct FXO_priv_data *priv; priv = xpd->priv; BUG_ON(!priv); for_each_line(xpd, i) { if(priv->ring_sig[i]) CALL_PROTO(FXO, DAA_QUERY, xbus, xpd, i, DAA_RING_REGISTER); } } #endif static int FXO_card_tick(xbus_t *xbus, xpd_t *xpd) { struct FXO_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; BUG_ON(!priv); if(poll_battery_interval != 0 && (priv->poll_counter % poll_battery_interval) == 0) { poll_battery(xbus, xpd); } #ifdef SOFT_RING if((priv->poll_counter % POLL_RING_INTERVAL) == 0) poll_ring(xbus, xpd); #endif handle_fxo_leds(xpd); priv->poll_counter++; return 0; } /* FIXME: based on data from from wctdm.h */ #include static const int echotune_reg[] = {30,45,46,47,58,49,50,51,52}; union echotune { /* "coeff 0" is acim */ unsigned char coeff[sizeof(echotune_reg)]; struct wctdm_echo_coefs wctdm_struct; }; static int FXO_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd, unsigned long arg) { union echotune echoregs; int i,ret; BUG_ON(!xpd); DBG("cmd: 0x%X, expecting: 0x%X, pos=%d.\n", cmd, WCTDM_SET_ECHOTUNE, pos); switch (cmd) { case WCTDM_SET_ECHOTUNE: DBG("-- Setting echo registers: \n"); /* first off: check if this span is fxs. If not: -EINVALID */ if (copy_from_user(&echoregs.wctdm_struct, (struct wctdm_echo_coefs __user *)arg, sizeof(echoregs.wctdm_struct))) return -EFAULT; /* Set the ACIM register */ /* quick and dirty registers writing: */ for (i=0; i> 8) & 0xFF), ((lines >> 16) & 0xFF), ((lines >> 24) & 0xFF), echotune_reg[i],echoregs.coeff[i] ); /* FIXME: code duplicated from proc_xpd_register_write */ ret = process_slic_cmdline(xpd, buf); if(ret < 0) return ret; mdelay(1); } DBG("-- Set echo registers successfully\n"); break; default: return -ENOTTY; } return 0; } /*---------------- FXO: HOST COMMANDS -------------------------------------*/ static /* 0x0F */ HOSTCMD(FXO, CHAN_ENABLE, xpp_line_t lines, bool on) { int ret = 0; int i; BUG_ON(!xbus); BUG_ON(!xpd); if(!lines) { return 0; } DBG("Channel Activation: 0x%4X %s\n", lines, (on) ? "on" : "off"); if(on) { for_each_line(xpd, i) { MARK_LED(xpd, i, LED_GREEN, LED_ON); mdelay(20); } for_each_line(xpd, i) { MARK_LED(xpd, i, LED_GREEN, LED_OFF); mdelay(20); } } return ret; } static /* 0x0F */ HOSTCMD(FXO, CHAN_CID, int pos) { int ret = 0; xpp_line_t lines = BIT(pos); BUG_ON(!xbus); BUG_ON(!xpd); if(!lines) { return 0; } DBG("%s/%s/%d:\n", xbus->busname, xpd->xpdname, pos); return ret; } static /* 0x0F */ HOSTCMD(FXO, RING, int pos, bool on) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; xpp_line_t mask = BIT(pos); int len; BUG_ON(!xbus); BUG_ON(!xpd); if(!mask) { return 0; } DBG("%s/%s/%d %s\n", xpd->xbus->busname, xpd->xpdname, pos, (on) ? "on" : "off"); XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd); len = slic_cmd_direct_write(sc, mask, 0x40, (on)?0x04:0x01); pack->datalen = len; packet_send(xbus, pack); return ret; } static /* 0x0F */ HOSTCMD(FXO, RELAY_OUT, byte which, bool on) { return -ENOSYS; } static /* 0x0F */ HOSTCMD(FXO, DAA_QUERY, int pos, byte reg_num) { int ret = 0; xpacket_t *pack; slic_cmd_t *sc; int len; BUG_ON(!xbus); BUG_ON(!xpd); // DBG("\n"); XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd); len = slic_cmd_direct_read(sc, BIT(pos), reg_num); pack->datalen = len; packet_send(xbus, pack); return ret; } /*---------------- FXO: Astribank Reply Handlers --------------------------*/ HANDLER_DEF(FXO, SIG_CHANGED) { xpp_line_t sig_status = RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_status); xpp_line_t sig_toggles = RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_toggles); unsigned long flags; int i; struct FXO_priv_data *priv; if(!xpd) { NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, XPD_NUM(pack->content.addr)); return -EPROTO; } priv = xpd->priv; BUG_ON(!priv); DBG("%s/%s: (PSTN) sig_toggles=0x%04X sig_status=0x%04X\n", xpd->xbus->busname, xpd->xpdname, sig_toggles, sig_status); spin_lock_irqsave(&xpd->lock, flags); for_each_line(xpd, i) { if(IS_SET(sig_toggles, i)) { if(!IS_SET(priv->battery, i)) { DBG("%s/%s/%d: battery is off. ignore false alarm.\n", xbus->busname, xpd->xpdname, i); continue; } if(IS_SET(sig_status, i)) { #ifdef SOFT_RING priv->ring_sig[i]=1; /* trigger register polling */ /* reset ring check counters */ priv->ring_thresh[i] = 0; priv->noring_thresh[i] = 0; #else mark_ring(xpd, i, 1); #endif } else { #ifdef SOFT_RING priv->ring_sig[i] = 0; #endif mark_ring(xpd, i, 0); } } } spin_unlock_irqrestore(&xpd->lock, flags); return 0; } HANDLER_DEF(FXO, DAA_REPLY) { slic_reply_t *info = &RPACKET_FIELD(pack, FXO, DAA_REPLY, info); xpp_line_t lines = RPACKET_FIELD(pack, FXO, DAA_REPLY, lines); unsigned long flags; struct FXO_priv_data *priv; if(!xpd) { NOTICE("%s: received %s for non-existing xpd: %d\n", __FUNCTION__, cmd->name, XPD_NUM(pack->content.addr)); return -EPROTO; } spin_lock_irqsave(&xpd->lock, flags); priv = xpd->priv; BUG_ON(!priv); if(!info->indirect && info->reg_num == DAA_VBAT_REGISTER) { byte bat = abs((signed char)info->data_low); int i; for_each_line(xpd, i) { if(!IS_SET(lines, i)) continue; if(bat < BAT_THRESHOLD) { /* * Check for battery voltage fluctuations */ if(IS_SET(priv->battery, i) && priv->battery_debounce[i]++ > BAT_DEBOUNCE) { DBG("%s/%s: BATTERY OFF (%04X) voltage=%d\n", xpd->xbus->busname, xpd->xpdname, lines, bat); BIT_CLR(priv->battery, i); update_line_status(xpd, i, 0); } } else { priv->battery_debounce[i] = 0; if(!IS_SET(priv->battery, i)) { DBG("%s/%s: BATTERY ON (%04X) voltage=%d\n", xpd->xbus->busname, xpd->xpdname, lines, bat); BIT_SET(priv->battery, i); } } } } #ifdef SOFT_RING if(!info->indirect && info->reg_num == DAA_RING_REGISTER) { bool ringit = (info->data_low & (0x20 | 0x40)) ? 1 : 0; /* Ring positive | Ring negative */ int i; for_each_line(xpd, i) { if(!IS_SET(lines, i)) continue; if (!priv->ring_sig[i]) continue; if(ringit) { if(priv->ring_thresh[i] > RING_THRESHOLD) { mark_ring(xpd, i, 1); priv->noring_thresh[i] = 0; } else priv->ring_thresh[i]++; } else { if(priv->noring_thresh[i] > NORING_THRESHOLD) { mark_ring(xpd, i, 0); priv->ring_thresh[i] = 0; } else priv->noring_thresh[i]++; } } } #endif #if 0 if (info->reg_num != 29) DBG("DAA_REPLY: xpd #%d %s reg_num=0x%X, dataL=0x%X dataH=0x%X\n", xpd->id, (info->indirect)?"I":"D", info->reg_num, info->data_low, info->data_high); #endif /* Update /proc info only if reply relate to the last slic read request */ if(priv->requested_reply.indirect == info->indirect && priv->requested_reply.reg_num == info->reg_num) { priv->last_reply = *info; } spin_unlock_irqrestore(&xpd->lock, flags); return 0; } xproto_table_t PROTO_TABLE(FXO) = { .owner = THIS_MODULE, .entries = { /* Card Opcode */ XENTRY( FXO, SIG_CHANGED ), XENTRY( FXO, DAA_REPLY ), }, .name = "FXO", .type = XPD_TYPE_FXO, .xops = { .card_new = FXO_card_new, .card_init = FXO_card_init, .card_remove = FXO_card_remove, .card_zaptel_preregistration = FXO_card_zaptel_preregistration, .card_zaptel_postregistration = FXO_card_zaptel_postregistration, .card_hooksig = FXO_card_hooksig, .card_tick = FXO_card_tick, .card_ioctl = FXO_card_ioctl, .RING = XPROTO_CALLER(FXO, RING), .RELAY_OUT = XPROTO_CALLER(FXO, RELAY_OUT), .CHAN_ENABLE = XPROTO_CALLER(FXO, CHAN_ENABLE), .CHAN_CID = XPROTO_CALLER(FXO, CHAN_CID), .SYNC_SOURCE = XPROTO_CALLER(GLOBAL, SYNC_SOURCE), .PCM_WRITE = XPROTO_CALLER(GLOBAL, PCM_WRITE), }, .packet_is_valid = fxo_packet_is_valid, .packet_dump = fxo_packet_dump, }; static bool fxo_packet_is_valid(xpacket_t *pack) { const xproto_entry_t *xe; //DBG("\n"); xe = xproto_card_entry(&PROTO_TABLE(FXO), pack->content.opcode); return xe != NULL; } static void fxo_packet_dump(xpacket_t *pack) { DBG("\n"); } /*------------------------- DAA Handling --------------------------*/ static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; unsigned long flags; xpd_t *xpd = data; struct FXO_priv_data *priv; int i; if(!xpd) return -ENODEV; spin_lock_irqsave(&xpd->lock, flags); priv = xpd->priv; BUG_ON(!priv); len += sprintf(page + len, "\t%-17s: ", "Channel"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%2d ", i % 10); } len += sprintf(page + len, "\n\t%-17s: ", "ledstate"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%2d ", IS_SET(priv->ledstate[LED_GREEN], i)); } len += sprintf(page + len, "\n\t%-17s: ", "ledcontrol"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%2d ", IS_SET(priv->ledcontrol[LED_GREEN], i)); } len += sprintf(page + len, "\n\t%-17s: ", "blinking"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%2d ", IS_BLINKING(priv,i,LED_GREEN)); } #ifdef SOFT_RING len += sprintf(page + len, "\n\t%-17s: ", "ring_thresh"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%2d ", priv->ring_thresh[i]); } len += sprintf(page + len, "\n\t%-17s: ", "noring_thresh"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%2d ", priv->noring_thresh[i]); } len += sprintf(page + len, "\n\t%-17s: ", "ring_sig"); for_each_line(xpd, i) { if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) len += sprintf(page + len, "%d ", priv->ring_sig[i]); } #endif len += sprintf(page + len, "\n\t%-17s: ", "battery"); for_each_line(xpd, i) { len += sprintf(page + len, "%2d ", IS_SET(priv->battery, i)); } len += sprintf(page + len, "\n\t%-17s: ", "battery_debounce"); for_each_line(xpd, i) { len += sprintf(page + len, "%2d ", priv->battery_debounce[i]); } len += sprintf(page + len, "\n"); spin_unlock_irqrestore(&xpd->lock, flags); if (len <= off+count) *eof = 1; *start = page + off; len -= off; if (len > count) len = count; if (len < 0) len = 0; return len; } static int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; unsigned long flags; xpd_t *xpd = data; slic_reply_t *info; struct FXO_priv_data *priv; BUG_ON(!xpd); spin_lock_irqsave(&xpd->lock, flags); priv = xpd->priv; BUG_ON(!priv); info = &priv->last_reply; len += sprintf(page + len, "# Writing bad data into this file may damage your hardware!\n"); len += sprintf(page + len, "# Consult firmware docs first\n"); len += sprintf(page + len, "DAA_REPLY: %s reg_num=0x%X, dataH=0x%X dataL=0x%X\n", (info->indirect)?"I":"D", info->reg_num, info->data_high, info->data_low); spin_unlock_irqrestore(&xpd->lock, flags); if (len <= off+count) *eof = 1; *start = page + off; len -= off; if (len > count) len = count; if (len < 0) len = 0; return len; } /* * Direct/Indirect * v * FF FF FF FF WD 06 1 * ^---------^ ^ Reg * | Write/Read * | * DAA # */ static int parse_slic_cmd(const char *buf, slic_cmd_t *sc, slic_reply_t *requested_reply) { char op; /* [W]rite, [R]ead */ char reg_type; /* [D]irect, [I]ndirect */ int s1, s2, s3, s4; int reg_num; int data_low, data_high; xpp_line_t lines; int ret; ret = sscanf(buf, "%x %x %x %x %c%c %x %x %x", &s1, &s2, &s3, &s4, &op, ®_type, ®_num, &data_high, &data_low); lines = (s4 << 24) | (s3 << 16) | (s2 << 8) | (s1); switch(op) { case 'R': if(reg_type == 'D' && ret == 7) { // DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num); ret = slic_cmd_direct_read(sc, lines, reg_num); if(requested_reply) { requested_reply->indirect = 0; requested_reply->reg_num = reg_num; } } else if(reg_type == 'I' && ret == 7) { // DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num); ret = slic_cmd_indirect_read(sc, lines, reg_num); if(requested_reply) { requested_reply->indirect = 1; requested_reply->reg_num = reg_num; } } else { NOTICE("%s: Bad read input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type); goto err; } break; case 'W': if(reg_type == 'D' && ret == 8) { // DBG("0x%X 0x%X 0x%X 0x%X %c %x %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high); ret = slic_cmd_direct_write(sc, lines, reg_num, data_high); } else if(reg_type == 'I' && ret == 9) { // DBG("0x%X 0x%X 0x%X 0x%X %c %x %X %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high, data_low); ret = slic_cmd_indirect_write(sc, lines, reg_num, data_low, data_high); } else { NOTICE("%s: Bad write input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type); goto err; } break; default: NOTICE("%s: Bad input: ret=%d buf='%s' op=%c\n", __FUNCTION__, ret, buf, op); goto err; } return ret; err: return -EINVAL; } static int process_slic_cmdline(xpd_t *xpd, char *cmdline) { xbus_t *xbus; struct FXO_priv_data *priv; slic_cmd_t sc; xpacket_t *pack; char *p; int len = strlen(cmdline); BUG_ON(!xpd); xbus = xpd->xbus; priv = xpd->priv; if((p = strchr(cmdline, '#')) != NULL) /* Truncate comments */ *p = '\0'; if((p = strchr(cmdline, ';')) != NULL) /* Truncate comments */ *p = '\0'; for(p = cmdline; *p && (*p == ' ' || *p == '\t'); p++) /* Trim leading whitespace */ ; if(*p == '\0') return 0; len = parse_slic_cmd(p, &sc, &priv->requested_reply); if(len < 0) return len; if(!sc.lines) { NOTICE("%s: no channels are marked. Skip.\n", __FUNCTION__); return 0; } dump_slic_cmd("WRITE_DAA", &sc); XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd) = sc; pack->datalen = len; packet_send(xbus, pack); return 0; } static int proc_xpd_slic_write(struct file *file, const char __user *buffer, unsigned long count, void *data) { xpd_t *xpd = data; char buf[MAX_PROC_WRITE]; char *p; int i; int ret; if(!xpd) return -ENODEV; for(i = 0; i < count; /* noop */) { for(p = buf; p < buf + MAX_PROC_WRITE; p++) { /* read a line */ if(i >= count) break; if(get_user(*p, buffer + i)) return -EFAULT; i++; if(*p == '\n' || *p == '\r') /* whatever */ break; } if(p >= buf + MAX_PROC_WRITE) return -E2BIG; *p = '\0'; ret = process_slic_cmdline(xpd, buf); if(ret < 0) return ret; mdelay(1); } return count; } int __init card_fxo_startup(void) { INFO("%s revision %s\n", THIS_MODULE->name, ZAPTEL_VERSION); #ifdef SOFT_RING INFO("FEATURE: %s with SOFT_RING\n", THIS_MODULE->name); #else INFO("FEATURE: %s without SOFT_RING\n", THIS_MODULE->name); #endif xproto_register(&PROTO_TABLE(FXO)); return 0; } void __exit card_fxo_cleanup(void) { xproto_unregister(&PROTO_TABLE(FXO)); } MODULE_DESCRIPTION("XPP FXO Card Driver"); MODULE_AUTHOR("Oron Peled "); MODULE_LICENSE("GPL"); MODULE_VERSION(ZAPTEL_VERSION); MODULE_ALIAS_XPD(XPD_TYPE_FXO); module_init(card_fxo_startup); module_exit(card_fxo_cleanup);