/* * 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 "xpd.h" #include "xproto.h" #include "xpp_zap.h" #include "card_fxo.h" #include "zap_debug.h" #include "xbus-core.h" static const char rcsid[] = "$Id$"; DEF_PARM(int, print_dbg, 0, 0644, "Print DBG statements"); DEF_PARM(uint, poll_battery_interval, 500, 0644, "Poll battery interval in milliseconds (0 - disable)"); #ifdef WITH_METERING DEF_PARM(uint, poll_metering_interval, 500, 0644, "Poll metering interval in milliseconds (0 - disable)"); #endif DEF_PARM(int, ring_debounce, 50, 0644, "Number of ticks to debounce a false RING indication"); /* 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) */ static /* 0x0F */ DECLARE_CMD(FXO, REGISTER_REQUEST, byte chipsel, bool writing, bool do_subreg, byte regnum, byte subreg, byte data_low, byte data_high); /* Shortcuts */ #define DAA_WRITE 1 #define DAA_READ 0 #define DAA_DIRECT_REQUEST(xbus,xpd,chipsel,writing,reg,dL) \ CALL_PROTO(FXO, REGISTER_REQUEST, (xbus), (xpd), (chipsel), (writing), 0, (reg), 0, (dL), 0) #define VALID_CHIPSEL(x) (((chipsel) >= 0 && (chipsel) <= 7) || (chipsel) == ALL_CHANS) /*---------------- FXO Protocol Commands ----------------------------------*/ static /* 0x0F */ DECLARE_CMD(FXO, XPD_STATE, bool on); static /* 0x0F */ DECLARE_CMD(FXO, RING, lineno_t chan, bool on); static /* 0x0F */ DECLARE_CMD(FXO, RELAY_OUT, byte which, bool on); static bool fxo_packet_is_valid(xpacket_t *pack); static void fxo_packet_dump(const char *msg, xpacket_t *pack); static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data); #ifdef WITH_METERING static int proc_xpd_metering_read(char *page, char **start, off_t off, int count, int *eof, void *data); #endif static int proc_xpd_register_read(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_xpd_register_write(struct file *file, const char __user *buffer, unsigned long count, void *data); static int handle_register_command(xpd_t *xpd, char *cmdline); #define PROC_REGISTER_FNAME "slics" #define PROC_FXO_INFO_FNAME "fxo_info" #ifdef WITH_METERING #define PROC_METERING_FNAME "metering_read" #endif #define DAA_RING_REGISTER 0x05 #define DAA_METERING_REGISTER 0x11 /* 17 */ #define DAA_CURRENT_REGISTER 0x1C /* 28 */ #define POWER_DENIAL_CURRENT 3 #define POWER_DENIAL_TIME 1000 /* ticks */ struct FXO_priv_data { struct proc_dir_entry *regfile; #ifdef WITH_METERING struct proc_dir_entry *meteringfile; #endif struct proc_dir_entry *fxo_info; uint poll_counter; xpp_line_t battery; ushort battery_debounce[CHANNELS_PERXPD]; xpp_line_t polarity; ushort polarity_counter[CHANNELS_PERXPD]; uint offhook_timestamp[CHANNELS_PERXPD]; ushort current_counter[CHANNELS_PERXPD]; xpp_line_t ledstate[NUM_LEDS]; /* 0 - OFF, 1 - ON */ xpp_line_t ledcontrol[NUM_LEDS]; /* 0 - OFF, 1 - ON */ int led_counter[NUM_LEDS][CHANNELS_PERXPD]; atomic_t ring_debounce[CHANNELS_PERXPD]; #ifdef WITH_METERING uint metering_count[CHANNELS_PERXPD]; xpp_line_t metering_tone_state; #endif }; /* * LED counter values: * n>1 : BLINK every n'th tick */ #define LED_COUNTER(priv,pos,color) ((priv)->led_counter[color][pos]) #define IS_BLINKING(priv,pos,color) (LED_COUNTER(priv,pos,color) > 0) #define MARK_BLINK(priv,pos,color,t) ((priv)->led_counter[color][pos] = (t)) #define MARK_OFF(priv,pos,color) do { BIT_CLR((priv)->ledcontrol[color],(pos)); MARK_BLINK((priv),(pos),(color),0); } while(0) #define MARK_ON(priv,pos,color) do { BIT_SET((priv)->ledcontrol[color],(pos)); MARK_BLINK((priv),(pos),(color),0); } while(0) #define LED_BLINK_RING (1000/8) /* in ticks */ /*---------------- FXO: Static functions ----------------------------------*/ /* * LED control is done via DAA register 0x20 */ static int do_led(xpd_t *xpd, lineno_t chan, byte which, bool on) { int ret = 0; struct FXO_priv_data *priv; xbus_t *xbus; BUG_ON(!xpd); xbus = xpd->xbus; priv = xpd->priv; which = which % NUM_LEDS; if(IS_SET(xpd->digital_outputs, chan) || IS_SET(xpd->digital_inputs, chan)) goto out; if(chan == ALL_CHANS) { priv->ledstate[which] = (on) ? ~0 : 0; } else { if(on) { BIT_SET(priv->ledstate[which], chan); } else { BIT_CLR(priv->ledstate[which], chan); } } LINE_DBG(LEDS, xpd, chan, "LED: which=%d -- %s\n", which, (on) ? "on" : "off"); ret = DAA_DIRECT_REQUEST(xbus, xpd, chan, DAA_WRITE, 0x20, on); 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(xpd->blink_mode || IS_BLINKING(priv,i,color)) { int mod_value = LED_COUNTER(priv, i, color); if(!mod_value) mod_value = DEFAULT_LED_PERIOD; /* safety value */ // led state is toggled if((timer_count % mod_value) == 0) { LINE_DBG(LEDS, xpd, i, "ledstate=%s\n", (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); } void update_zap_ring(xpd_t *xpd, int pos, bool on) { zt_rxsig_t rxsig; BUG_ON(!xpd); if(on) { BIT_CLR(xpd->cid_on, pos); rxsig = ZT_RXSIG_RING; } else { BIT_SET(xpd->cid_on, pos); rxsig = ZT_RXSIG_OFFHOOK; } pcm_recompute(xpd, xpd->offhook | xpd->cid_on); /* * We should not spinlock before calling zt_hooksig() as * it may call back into our xpp_hooksig() and cause * a nested spinlock scenario */ if(SPAN_REGISTERED(xpd)) zt_hooksig(&xpd->chans[pos], rxsig); } static void mark_ring(xpd_t *xpd, lineno_t pos, bool on, bool update_zap) { struct FXO_priv_data *priv; priv = xpd->priv; BUG_ON(!priv); atomic_set(&priv->ring_debounce[pos], 0); /* Stop debouncing */ if(on && !xpd->ringing[pos]) { LINE_DBG(SIGNAL, xpd, pos, "START\n"); xpd->ringing[pos] = 1; MARK_BLINK(priv, pos, LED_GREEN, LED_BLINK_RING); if(update_zap) update_zap_ring(xpd, pos, on); } else if(!on && xpd->ringing[pos]) { LINE_DBG(SIGNAL, xpd, pos, "STOP\n"); xpd->ringing[pos] = 0; if(IS_BLINKING(priv, pos, LED_GREEN)) MARK_BLINK(priv, pos, LED_GREEN, 0); if(update_zap) update_zap_ring(xpd, pos, on); } } 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; byte value; 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)) { LINE_DBG(SIGNAL, xpd, pos, "WARNING: called while battery is off\n"); } spin_lock_irqsave(&xpd->lock, flags); mark_ring(xpd, pos, 0, 0); // No more rings value = (to_offhook) ? 0x09 : 0x08; /* Bit 3 is for CID */ LINE_DBG(SIGNAL, xpd, pos, "SETHOOK: value=0x%02X %s\n", value, (to_offhook)?"OFFHOOK":"ONHOOK"); if(to_offhook) MARK_ON(priv, pos, LED_GREEN); else MARK_OFF(priv, pos, LED_GREEN); ret = DAA_DIRECT_REQUEST(xbus, xpd, pos, DAA_WRITE, DAA_RING_REGISTER, value); if(to_offhook) { BIT_SET(xpd->offhook, pos); priv->offhook_timestamp[pos] = priv->poll_counter; } else { BIT_CLR(xpd->offhook, pos); BIT_CLR(xpd->cid_on, pos); } #ifdef WITH_METERING priv->metering_count[pos] = 0; priv->metering_tone_state = 0L; DAA_DIRECT_REQUEST(xbus, xpd, pos, DAA_WRITE, DAA_METERING_REGISTER, 0x2D); #endif spin_unlock_irqrestore(&xpd->lock, flags); return ret; } /*---------------- FXO: Methods -------------------------------------------*/ static xpd_t *FXO_card_new(xbus_t *xbus, int unit, int subunit, const xproto_table_t *proto_table, byte subtype, byte revision) { xpd_t *xpd = NULL; int channels; if(subtype == 2) channels = min(2, CHANNELS_PERXPD); else channels = min(8, CHANNELS_PERXPD); xpd = xpd_alloc(sizeof(struct FXO_priv_data), proto_table, channels); if(!xpd) return NULL; xpd->direction = TO_PSTN; xpd->revision = revision; xpd->type_name = proto_table->name; return xpd; } static void clean_proc(xbus_t *xbus, xpd_t *xpd) { struct FXO_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; XPD_DBG(PROC, xpd, "\n"); #ifdef CONFIG_PROC_FS if(priv->regfile) { XPD_DBG(PROC, xpd, "Removing xpd DAA file\n"); remove_proc_entry(PROC_REGISTER_FNAME, xpd->proc_xpd_dir); priv->regfile->data = NULL; } #ifdef WITH_METERING if(priv->meteringfile) { XPD_DBG(PROC, xpd, "Removing xpd metering tone file\n"); priv->meteringfile->data = NULL; remove_proc_entry(PROC_METERING_FNAME, xpd->proc_xpd_dir); priv->meteringfile = NULL; } #endif if(priv->fxo_info) { XPD_DBG(PROC, xpd, "Removing xpd FXO_INFO file\n"); 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 XPD_DBG(PROC, xpd, "Creating FXO_INFO file\n"); 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) { XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_FXO_INFO_FNAME); ret = -ENOENT; goto err; } priv->fxo_info->owner = THIS_MODULE; #ifdef WITH_METERING XPD_DBG(PROC, xpd, "Creating Metering tone file\n"); priv->meteringfile = create_proc_read_entry(PROC_METERING_FNAME, 0444, xpd->proc_xpd_dir, proc_xpd_metering_read, xpd); if(!priv->meteringfile) { XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_METERING_FNAME); ret = -ENOENT; goto err; } priv->meteringfile->owner = THIS_MODULE; #endif XPD_DBG(PROC, xpd, "Creating DAAs file\n"); priv->regfile = create_proc_entry(PROC_REGISTER_FNAME, 0644, xpd->proc_xpd_dir); if(!priv->regfile) { XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_REGISTER_FNAME); ret = -ENOENT; goto err; } priv->regfile->owner = THIS_MODULE; priv->regfile->write_proc = proc_xpd_register_write; priv->regfile->read_proc = proc_xpd_register_read; priv->regfile->data = xpd; #endif ret = run_initialize_registers(xpd); if(ret < 0) goto err; // Hanghup all lines for_each_line(xpd, i) { do_sethook(xpd, i, 0); } XPD_DBG(GENERAL, xpd, "done\n"); for_each_line(xpd, i) { do_led(xpd, i, LED_GREEN, 0); } for_each_line(xpd, i) { do_led(xpd, i, LED_GREEN, 1); msleep(50); } for_each_line(xpd, i) { do_led(xpd, i, LED_GREEN, 0); msleep(50); } pcm_recompute(xpd, xpd->offhook | xpd->cid_on); return 0; err: clean_proc(xbus, xpd); XPD_ERR(xpd, "Failed initializing registers (%d)\n", 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; XPD_DBG(GENERAL, xpd, "\n"); 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); XPD_DBG(GENERAL, xpd, "%s\n", (on)?"ON":"OFF"); for_each_line(xpd, i) { struct zt_chan *cur_chan = &xpd->chans[i]; XPD_DBG(GENERAL, xpd, "setting FXO channel %d\n", i); snprintf(cur_chan->name, MAX_CHANNAME, "XPP_FXO/%02d/%1d%1d/%d", xbus->num, xpd->addr.unit, xpd->addr.subunit, i); cur_chan->chanpos = i + 1; cur_chan->pvt = xpd; cur_chan->sigcap = FXO_DEFAULT_SIGCAP; } for_each_line(xpd, i) { MARK_ON(priv, i, LED_GREEN); msleep(4); } 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); XPD_DBG(GENERAL, xpd, "%s\n", (on)?"ON":"OFF"); for_each_line(xpd, i) { MARK_OFF(priv, i, LED_GREEN); msleep(2); // MARK_OFF(priv, i, LED_RED); msleep(2); } 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); LINE_DBG(SIGNAL, xpd, pos, "%s\n", 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: XPD_NOTICE(xpd, "Can't set tx state to %s (%d)\n", txsig2str(txsig), txsig); return -EINVAL; } pcm_recompute(xpd, xpd->offhook | xpd->cid_on); return 0; } static int FXO_card_open(xpd_t *xpd, lineno_t chan) { struct FXO_priv_data *priv; BUG_ON(!xpd); priv = xpd->priv; /* * We pretend to have battery. If this is really the case * than next calls to update_battery_status() won't change it. * If we don't have battery, than on the next calls to * update_battery_status() a battery_debounce[] cycle would start. * Than, if no-battery is persistent, asterisk would be notified. */ BIT_SET(priv->battery, chan); return 0; } static void poll_battery(xbus_t *xbus, xpd_t *xpd) { int i; for_each_line(xpd, i) { DAA_DIRECT_REQUEST(xbus, xpd, i, DAA_READ, DAA_VBAT_REGISTER, 0); } } static void poll_current(xbus_t *xbus, xpd_t *xpd) { int i; for_each_line(xpd, i) { if (IS_SET(xpd->offhook, i)) DAA_DIRECT_REQUEST(xbus, xpd, i, DAA_READ, DAA_CURRENT_REGISTER, 0); } } #ifdef WITH_METERING static void poll_metering(xbus_t *xbus, xpd_t *xpd) { int i; for_each_line(xpd, i) { if (IS_SET(xpd->offhook, i)) DAA_DIRECT_REQUEST(xbus, xpd, i, DAA_READ, DAA_METERING_REGISTER, 0); } } #endif static void handle_fxo_ring(xpd_t *xpd) { struct FXO_priv_data *priv; int i; priv = xpd->priv; for_each_line(xpd, i) { if(atomic_read(&priv->ring_debounce[i]) > 0) { /* Maybe start ring */ if(atomic_dec_and_test(&priv->ring_debounce[i])) mark_ring(xpd, i, 1, 1); } else if (atomic_read(&priv->ring_debounce[i]) < 0) { /* Maybe stop ring */ if(atomic_inc_and_test(&priv->ring_debounce[i])) mark_ring(xpd, i, 0, 1); } } } 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); poll_current(xbus, xpd); } #ifdef WITH_METERING if(poll_metering_interval != 0 && (priv->poll_counter % poll_metering_interval) == 0) poll_metering(xbus, xpd); #endif handle_fxo_leds(xpd); handle_fxo_ring(xpd); priv->poll_counter++; return 0; } /* FIXME: based on data from from wctdm.h */ #include /* * The first register is the ACIM, the other are coefficient registers. * We define the array size explicitly to track possible inconsistencies * if the struct is modified. */ static const char echotune_regs[sizeof(struct wctdm_echo_coefs)] = {30, 45, 46, 47, 48, 49, 50, 51, 52}; static int FXO_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd, unsigned long arg) { int i,ret; unsigned char echotune_data[ARRAY_SIZE(echotune_regs)]; BUG_ON(!xpd); switch (cmd) { case WCTDM_SET_ECHOTUNE: XPD_DBG(GENERAL, xpd, "-- Setting echo registers: \n"); /* first off: check if this span is fxs. If not: -EINVALID */ if (copy_from_user(&echotune_data, (void __user *)arg, sizeof(echotune_data))) return -EFAULT; for (i = 0; i < ARRAY_SIZE(echotune_regs); i++) { XPD_DBG(REGS, xpd, "Reg=0x%02X, data=0x%02X\n", echotune_regs[i], echotune_data[i]); ret = DAA_DIRECT_REQUEST(xpd->xbus, xpd, pos, DAA_WRITE, echotune_regs[i], echotune_data[i]); if (ret < 0) { LINE_NOTICE(xpd, pos, "Couldn't write %0x02X to register %0x02X\n", echotune_data[i], echotune_regs[i]); return ret; } msleep(1); } XPD_DBG(GENERAL, xpd, "-- Set echo registers successfully\n"); break; default: LINE_DBG(GENERAL, xpd, pos, "Unknown command 0x%X.\n", cmd); return -ENOTTY; } return 0; } /*---------------- FXO: HOST COMMANDS -------------------------------------*/ /* 0x0F */ HOSTCMD(FXO, REGISTER_REQUEST, byte chipsel, bool writing, bool do_subreg, byte regnum, byte subreg, byte data_low, byte data_high) { int ret = 0; xframe_t *xframe; xpacket_t *pack; reg_cmd_t *reg_cmd; if(!xbus) { DBG(REGS, "NO XBUS\n"); return -EINVAL; } XFRAME_NEW(xframe, pack, xbus, GLOBAL, REGISTER_REQUEST, xpd->xbus_idx); LINE_DBG(REGS, xpd, chipsel, "%c%c R%02X S%02X %02X %02X\n", (writing)?'W':'R', (do_subreg)?'S':'D', regnum, subreg, data_low, data_high); reg_cmd = &RPACKET_FIELD(pack, GLOBAL, REGISTER_REQUEST, reg_cmd); reg_cmd->bytes = sizeof(*reg_cmd) - 1; // do not count the 'bytes' field REG_FIELD(reg_cmd, chipsel) = chipsel; REG_FIELD(reg_cmd, read_request) = (writing) ? 0 : 1; REG_FIELD(reg_cmd, do_subreg) = do_subreg; REG_FIELD(reg_cmd, regnum) = regnum; REG_FIELD(reg_cmd, subreg) = subreg; REG_FIELD(reg_cmd, data_low) = data_low; REG_FIELD(reg_cmd, data_high) = data_high; ret = send_cmd_frame(xbus, xframe); return ret; } static /* 0x0F */ HOSTCMD(FXO, XPD_STATE, bool on) { int ret = 0; struct FXO_priv_data *priv; BUG_ON(!xbus); BUG_ON(!xpd); priv = xpd->priv; BUG_ON(!priv); XPD_DBG(GENERAL, xpd, "%s\n", (on) ? "on" : "off"); return ret; } static /* 0x0F */ HOSTCMD(FXO, RING, lineno_t chan, bool on) { BUG_ON(!xbus); BUG_ON(!xpd); LINE_DBG(SIGNAL, xpd, chan, "%s\n", (on) ? "on" : "off"); return DAA_DIRECT_REQUEST(xbus, xpd, chan, DAA_WRITE, 0x40, (on)?0x04:0x01); } static /* 0x0F */ HOSTCMD(FXO, RELAY_OUT, byte which, bool on) { return -ENOSYS; } /*---------------- 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) { notify_bad_xpd(__FUNCTION__, xbus, pack->addr, cmd->name); return -EPROTO; } priv = xpd->priv; BUG_ON(!priv); XPD_DBG(SIGNAL, xpd, "(PSTN) sig_toggles=0x%04X sig_status=0x%04X\n", sig_toggles, sig_status); spin_lock_irqsave(&xpd->lock, flags); for_each_line(xpd, i) { int debounce; if(IS_SET(sig_toggles, i)) { if(!IS_SET(priv->battery, i)) { LINE_DBG(SIGNAL, xpd, i, "SIG_CHANGED while battery is off.\n"); // FIXME: allow dialing without battery polling... // continue; } /* First report false ring alarms */ debounce = atomic_read(&priv->ring_debounce[i]); if(debounce) LINE_NOTICE(xpd, i, "debounced %d ticks\n", debounce); /* * Now set a new ring alarm. * It will be checked in handle_fxo_ring() */ debounce = (IS_SET(sig_status, i)) ? ring_debounce : -ring_debounce; atomic_set(&priv->ring_debounce[i], debounce); } } spin_unlock_irqrestore(&xpd->lock, flags); return 0; } static void update_battery_status(xpd_t *xpd, byte data_low, lineno_t chipsel) { struct FXO_priv_data *priv; byte bat = abs((signed char)data_low); byte pol = IS_SET(data_low, 7); priv = xpd->priv; BUG_ON(!priv); if(bat < BAT_THRESHOLD) { /* * Check for battery voltage fluctuations */ if(IS_SET(priv->battery, chipsel) && priv->battery_debounce[chipsel]++ > BAT_DEBOUNCE) { LINE_DBG(SIGNAL, xpd, chipsel, "BATTERY OFF voltage=%d\n", bat); BIT_CLR(priv->battery, chipsel); if(SPAN_REGISTERED(xpd)) zt_qevent_lock(&xpd->chans[chipsel], ZT_EVENT_ALARM); } } else { priv->battery_debounce[chipsel] = 0; if(!IS_SET(priv->battery, chipsel)) { LINE_DBG(SIGNAL, xpd, chipsel, "BATTERY ON voltage=%d\n", bat); BIT_SET(priv->battery, chipsel); if(SPAN_REGISTERED(xpd)) zt_qevent_lock(&xpd->chans[chipsel], ZT_EVENT_NOALARM); } } /* * Handle reverse polarity */ if (IS_SET(xpd->offhook, chipsel)) { /* Learn the current polarity */ if (priv->poll_counter - priv->offhook_timestamp[chipsel] < 3) { priv->polarity_counter[chipsel] = 0; if (pol) BIT_SET(priv->polarity, chipsel); else BIT_CLR(priv->polarity, chipsel); } else if (IS_SET(priv->polarity, chipsel) != pol) { /* Polarity has reversed */ priv->polarity_counter[chipsel]++; if (priv->polarity_counter[chipsel] >= 2) { if (pol) BIT_SET(priv->polarity, chipsel); else BIT_CLR(priv->polarity, chipsel); priv->polarity_counter[chipsel] = 0; /* Inform Zaptel */ LINE_DBG(GENERAL, xpd, chipsel, "Send ZT_EVENT_POLARITY\n"); zt_qevent_lock(&xpd->chans[chipsel], ZT_EVENT_POLARITY); #if 0 /* * These two lines hangup the channel (by sending a message to * the firmware), and inform Zaptel that the line has been hung-up. * They are not needed if Asterisk does the hangup after receiving * a notification from Zaptel (which is sent by the above zt_qevent_lock(). * Asterisk does that if it has "hanguponpolarityswitch=1" in zapata.conf. */ do_sethook(xpd, chipsel, 0); update_line_status(xpd, chipsel, 0); pcm_recompute(xpd, xpd->offhook | xpd->cid_on); #endif } } } } static void update_power_denial(xpd_t *xpd, byte data_low, lineno_t chipsel) { struct FXO_priv_data *priv; priv = xpd->priv; BUG_ON(!priv); if (IS_SET(xpd->offhook, chipsel) && data_low < POWER_DENIAL_CURRENT) { /* Current dropped */ priv->current_counter[chipsel]++; if (priv->current_counter[chipsel] * poll_battery_interval >= POWER_DENIAL_TIME) { LINE_DBG(SIGNAL, xpd, chipsel, "Power Denial Hangup\n"); priv->current_counter[chipsel] = 0; do_sethook(xpd, chipsel, 0); update_line_status(xpd, chipsel, 0); pcm_recompute(xpd, xpd->offhook | xpd->cid_on); } } else priv->current_counter[chipsel] = 0; } #ifdef WITH_METERING #define BTD_BIT BIT(0) static void update_metering_state(xpd_t *xpd, byte data_low, lineno_t chipsel) { struct FXO_priv_data *priv; bool metering_tone = data_low & BTD_BIT; bool old_metering_tone; priv = xpd->priv; BUG_ON(!priv); old_metering_tone = IS_SET(priv->metering_tone_state, chipsel); LINE_DBG(SIGNAL, xpd, chipsel, "METERING: %s [dL=0x%X] (%d)\n", (metering_tone) ? "ON" : "OFF", data_low, priv->metering_count[chipsel]); if(metering_tone && !old_metering_tone) { /* Rising edge */ priv->metering_count[chipsel]++; BIT_SET(priv->metering_tone_state, chipsel); } else if(!metering_tone && old_metering_tone) BIT_CLR(priv->metering_tone_state, chipsel); if(metering_tone) { /* Clear the BTD bit */ data_low &= ~BTD_BIT; DAA_DIRECT_REQUEST(xpd->xbus, xpd, chipsel, DAA_WRITE, DAA_METERING_REGISTER, data_low); } } #endif HANDLER_DEF(FXO, DAA_REPLY) { reg_cmd_t *info = &RPACKET_FIELD(pack, FXO, DAA_REPLY, regcmd); struct FXO_priv_data *priv; lineno_t chipsel; if(!xpd) { notify_bad_xpd(__FUNCTION__, xbus, pack->addr, cmd->name); return -EPROTO; } priv = xpd->priv; BUG_ON(!priv); chipsel = REG_FIELD(info, chipsel); switch(REG_FIELD(info, regnum)) { case DAA_VBAT_REGISTER: update_battery_status(xpd, REG_FIELD(info, data_low), chipsel); break; case DAA_CURRENT_REGISTER: update_power_denial(xpd, REG_FIELD(info, data_low), chipsel); break; #ifdef WITH_METERING case DAA_METERING_REGISTER: update_metering_state(xpd, REG_FIELD(info, data_low), chipsel); break; #endif } LINE_DBG(REGS, xpd, chipsel, "DAA_REPLY: %c reg_num=0x%X, dataL=0x%X dataH=0x%X\n", ((info->bytes == 3)?'I':'D'), REG_FIELD(info, regnum), REG_FIELD(info, data_low), REG_FIELD(info, data_high)); /* Update /proc info only if reply relate to the last slic 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; } return 0; } xproto_table_t PROTO_TABLE(FXO) = { .owner = THIS_MODULE, .entries = { /* Prototable Card Opcode */ XENTRY( FXO, FXO, SIG_CHANGED ), XENTRY( FXO, 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_pcm_fromspan = generic_card_pcm_fromspan, .card_pcm_tospan = generic_card_pcm_tospan, .card_ioctl = FXO_card_ioctl, .card_open = FXO_card_open, .RING = XPROTO_CALLER(FXO, RING), .RELAY_OUT = XPROTO_CALLER(FXO, RELAY_OUT), .XPD_STATE = XPROTO_CALLER(FXO, XPD_STATE), }, .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(GENERAL, "\n"); xe = xproto_card_entry(&PROTO_TABLE(FXO), pack->opcode); return xe != NULL; } static void fxo_packet_dump(const char *msg, xpacket_t *pack) { DBG(GENERAL, "%s\n", msg); } /*------------------------- 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: ", "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)); } 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: ", "polarity"); for_each_line(xpd, i) { len += sprintf(page + len, "%2d ", IS_SET(priv->polarity, i)); } #ifdef WITH_METERING len += sprintf(page + len, "\n\t%-17s: ", "metering"); for_each_line(xpd, i) { len += sprintf(page + len, "%2d ", priv->metering_count[i]); } #endif 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; } /* * * Direct/Indirect * | * | Reg# * | | * | | Data (only in Write) * | | | * | | +-+-+ * v v v v * FF WD 06 01 05 * ^ ^ * | | * | Write/Read * | * Chan# * */ static int handle_register_command(xpd_t *xpd, char *cmdline) { unsigned chipsel; unsigned data_low = 0; char op; /* [W]rite, [R]ead */ char reg_type; /* [D]irect */ int reg_num; int elements; bool writing; char *p; reg_cmd_t regcmd; xbus_t *xbus; int ret; BUG_ON(!xpd); xbus = xpd->xbus; 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; elements = sscanf(cmdline, "%d %c%c %x %x", &chipsel, &op, ®_type, ®_num, &data_low); XPD_DBG(PROC, xpd, "'%s': %d %c%c %02X %02X\n", cmdline, chipsel, op, reg_type, reg_num, data_low); if(elements < 4) { // At least: chipsel, op, reg_type, reg_num ERR("Not enough arguments: (%d args) '%s'\n", elements, cmdline); return -EINVAL; } if(!VALID_CHIPSEL(chipsel)) { ERR("Bad chipsel number: %d\n", chipsel); return -EINVAL; } REG_FIELD(®cmd, chipsel) = chipsel; REG_FIELD(®cmd, do_subreg) = 0; switch(op) { case 'W': writing = 1; break; case 'R': writing = 0; break; default: ERR("Unkown operation type '%c'\n", op); return -EINVAL; } switch(reg_type) { case 'D': REG_FIELD(®cmd, regnum) = reg_num; REG_FIELD(®cmd, subreg) = 0; break; default: ERR("Unkown register type '%c'\n", reg_type); return -EINVAL; } if( (op == 'W' && reg_type == 'D' && elements != 5) || (op == 'R' && reg_type == 'D' && elements != 4) ) { ERR("%s: '%s' (%d elements): %d %c%c %02X %02X\n", __FUNCTION__, cmdline, elements, chipsel, op, reg_type, reg_num, data_low); return -EINVAL; } regcmd.bytes = sizeof(regcmd) - 1; REG_FIELD(®cmd, data_low) = data_low; REG_FIELD(®cmd, data_high) = 0; REG_FIELD(®cmd, read_request) = writing; if(!down_read_trylock(&xbus->in_use)) { XBUS_DBG(GENERAL, xbus, "Dropped packet. Is in_use\n"); return -EBUSY; } xpd->requested_reply = regcmd; if(print_dbg) dump_reg_cmd("FXO", ®cmd, 1); ret = DAA_DIRECT_REQUEST(xpd->xbus, xpd, REG_FIELD(®cmd, chipsel), writing, REG_FIELD(®cmd, regnum), REG_FIELD(®cmd, data_low)); up_read(&xbus->in_use); return ret; } static int proc_xpd_register_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 = handle_register_command(xpd, buf); if(ret < 0) return ret; msleep(1); } return count; } static int proc_xpd_register_read(char *page, char **start, off_t off, int count, int *eof, void *data) { int len = 0; unsigned long flags; xpd_t *xpd = data; reg_cmd_t *info; byte regnum; if(!xpd) return -ENODEV; spin_lock_irqsave(&xpd->lock, flags); info = &xpd->last_reply; regnum = REG_FIELD(info, regnum); 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, "#\n"); len += sprintf(page + len, "#CH\tD/I\tReg.\tDL\n"); len += sprintf(page + len, "%2d\tRD\t%02X\t%02X\n", REG_FIELD(info, chipsel), regnum, REG_FIELD(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; } #ifdef WITH_METERING static int proc_xpd_metering_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; priv = xpd->priv; BUG_ON(!priv); spin_lock_irqsave(&xpd->lock, flags); len += sprintf(page + len, "# Chan\tMeter (since last read)\n"); for_each_line(xpd, i) { len += sprintf(page + len, "%d\t%d\n", i, priv->metering_count[i]); } 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; /* Zero meters */ for_each_line(xpd, i) priv->metering_count[i] = 0; return len; } #endif int __init card_fxo_startup(void) { if(ring_debounce <= 0) { ERR("ring_debounce=%d. Must be positive number of ticks\n", ring_debounce); return -EINVAL; } INFO("revision %s\n", XPP_VERSION); #ifdef WITH_METERING INFO("FEATURE: WITH METERING Detection\n"); #else INFO("FEATURE: NO METERING Detection\n"); #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(XPP_VERSION); MODULE_ALIAS_XPD(XPD_TYPE_FXO); module_init(card_fxo_startup); module_exit(card_fxo_cleanup);