diff options
Diffstat (limited to 'kernel/xpp/card_fxs.c')
-rw-r--r-- | kernel/xpp/card_fxs.c | 1620 |
1 files changed, 1620 insertions, 0 deletions
diff --git a/kernel/xpp/card_fxs.c b/kernel/xpp/card_fxs.c new file mode 100644 index 0000000..f2251e0 --- /dev/null +++ b/kernel/xpp/card_fxs.c @@ -0,0 +1,1620 @@ +/* + * Written by Oron Peled <oron@actcom.co.il> + * 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 <linux/init.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/delay.h> +#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"); /* must be before zap_debug.h */ +DEF_PARM(uint, poll_digital_inputs, 1000, 0644, "Poll Digital Inputs"); +DEF_PARM_BOOL(reversepolarity, 0, 0644, "Reverse Line Polarity"); +DEF_PARM_BOOL(vmwineon, 0, 0644, "Indicate voicemail to a neon lamp"); +DEF_PARM_BOOL(dtmf_detection, 0, 0644, "Do DTMF detection in hardware"); + +#ifdef ZT_VMWI +DEF_PARM_BOOL(vmwi_ioctl, 0, 0644, "Asterisk support VMWI notification via ioctl"); +#else +#define vmwi_ioctl 0 /* not supported */ +#endif + +/* Signaling is opposite (fxo signalling for fxs card) */ +#if 1 +#define FXS_DEFAULT_SIGCAP (ZT_SIG_FXOKS | ZT_SIG_FXOLS | ZT_SIG_FXOGS) +#else +#define FXS_DEFAULT_SIGCAP (ZT_SIG_SF | ZT_SIG_EM) +#endif + +#define LINES_DIGI_OUT 2 +#define LINES_DIGI_INP 4 + +enum fxs_leds { + LED_GREEN, + LED_RED, + OUTPUT_RELAY, +}; + +#define NUM_LEDS 2 + +/* Shortcuts */ +#define SLIC_WRITE 1 +#define SLIC_READ 0 +#define SLIC_DIRECT_REQUEST(xbus,xpd,chipsel,writing,reg,dL) \ + xpp_register_request((xbus), (xpd), (chipsel), (writing), 0, (reg), 0, (dL), 0) +#define SLIC_INDIRECT_REQUEST(xbus,xpd,chipsel,writing,reg,dL,dH) \ + xpp_register_request((xbus), (xpd), (chipsel), (writing), 1, 0x1E, (reg), (dL), (dH)) + +#define VALID_CHIPSEL(x) (((chipsel) >= 0 && (chipsel) <= 7) || (chipsel) == ALL_CHANS) + +/* Values of SLIC linefeed control register (0x40) */ +enum fxs_state { + FXS_LINE_OPEN = 0x00, /* Open */ + FXS_LINE_ACTIVE = 0x01, /* Forward active */ + FXS_LINE_OHTRANS = 0x02, /* Forward on-hook transmission */ + FXS_LINE_TIPOPEN = 0x03, /* TIP open */ + FXS_LINE_RING = 0x04, /* Ringing */ + FXS_LINE_REV_ACTIVE = 0x05, /* Reverse active */ + FXS_LINE_REV_OHTRANS = 0x06, /* Reverse on-hook transmission */ + FXS_LINE_RING_OPEN = 0x07 /* RING open */ +}; + +#define FXS_LINE_POL_ACTIVE ((reversepolarity) ? FXS_LINE_REV_ACTIVE : FXS_LINE_ACTIVE) +#define FXS_LINE_POL_OHTRANS ((reversepolarity) ? FXS_LINE_REV_OHTRANS : FXS_LINE_OHTRANS) + +/* + * DTMF detection + */ +#define SLIC_REG_DTMF 0x18 /* 24 */ +#define SLIC_REG_VOLTAGE 0x42 /* 66 */ + +/*---------------- FXS Protocol Commands ----------------------------------*/ + +static /* 0x0F */ DECLARE_CMD(FXS, XPD_STATE, bool on); +static /* 0x0F */ DECLARE_CMD(FXS, RING, lineno_t chan, bool on); +static /* 0x0F */ DECLARE_CMD(FXS, RELAY_OUT, byte which, bool on); + +static bool fxs_packet_is_valid(xpacket_t *pack); +static void fxs_packet_dump(const char *msg, xpacket_t *pack); +static int proc_fxs_info_read(char *page, char **start, off_t off, int count, int *eof, void *data); +#ifdef WITH_METERING +static int proc_xpd_metering_write(struct file *file, const char __user *buffer, unsigned long count, 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 void start_stop_vm_led(xbus_t *xbus, xpd_t *xpd, lineno_t pos); + +#define PROC_REGISTER_FNAME "slics" +#define PROC_FXS_INFO_FNAME "fxs_info" +#ifdef WITH_METERING +#define PROC_METERING_FNAME "metering_gen" +#endif + +struct FXS_priv_data { + struct proc_dir_entry *regfile; +#ifdef WITH_METERING + struct proc_dir_entry *meteringfile; +#endif + struct proc_dir_entry *fxs_info; + xpp_line_t ledstate[NUM_LEDS]; /* 0 - OFF, 1 - ON */ + xpp_line_t ledcontrol[NUM_LEDS]; /* 0 - OFF, 1 - ON */ + xpp_line_t search_fsk_pattern; + xpp_line_t found_fsk_pattern; + xpp_line_t update_offhook_state; + xpp_line_t want_dtmf_events; /* what zaptel want */ + xpp_line_t want_dtmf_mute; /* what zaptel want */ + int led_counter[NUM_LEDS][CHANNELS_PERXPD]; + int ohttimer[CHANNELS_PERXPD]; +#define OHT_TIMER 6000 /* How long after RING to retain OHT */ + enum fxs_state idletxhookstate[CHANNELS_PERXPD]; /* IDLE changing hook state */ + enum fxs_state lasttxhook[CHANNELS_PERXPD]; +}; + +/* + * 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 */ + +/*---------------- FXS: Static functions ----------------------------------*/ +static int linefeed_control(xbus_t *xbus, xpd_t *xpd, lineno_t chan, enum fxs_state value) +{ + struct FXS_priv_data *priv; + + priv = xpd->priv; + LINE_DBG(SIGNAL, xpd, chan, "value=0x%02X\n", value); + priv->lasttxhook[chan] = value; + return SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, 0x40, value); +} + +static int do_chan_power(xbus_t *xbus, xpd_t *xpd, lineno_t chan, bool on) +{ + int value = (on) ? 0x06 : 0x00; + + BUG_ON(!xbus); + BUG_ON(!xpd); + LINE_DBG(SIGNAL, xpd, chan, "%s\n", (on) ? "up" : "down"); + return SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, SLIC_REG_VOLTAGE, value); +} + +/* + * LED and RELAY control is done via SLIC register 0x06: + * 7 6 5 4 3 2 1 0 + * +-----+-----+-----+-----+-----+-----+-----+-----+ + * | M2 | M1 | M3 | C2 | O1 | O3 | C1 | C3 | + * +-----+-----+-----+-----+-----+-----+-----+-----+ + * + * Cn - Control bit (control one digital line) + * On - Output bit (program a digital line for output) + * Mn - Mask bit (only the matching output control bit is affected) + * + * C3 - OUTPUT RELAY (0 - OFF, 1 - ON) + * C1 - GREEN LED (0 - OFF, 1 - ON) + * O3 - Output RELAY (this line is output) + * O1 - Output GREEN (this line is output) + * C2 - RED LED (0 - OFF, 1 - ON) + * M3 - Mask RELAY. (1 - C3 effect the OUTPUT RELAY) + * M2 - Mask RED. (1 - C2 effect the RED LED) + * M1 - Mask GREEN. (1 - C1 effect the GREEN LED) + * + * The OUTPUT RELAY (actually a relay out) is connected to line 0 and 4 only. + */ + +// GREEN RED OUTPUT RELAY +static const int led_register_mask[] = { BIT(7), BIT(6), BIT(5) }; +static const int led_register_vals[] = { BIT(4), BIT(1), BIT(0) }; + +/* + * pos can be: + * - A line number + * - ALL_LINES. This is not valid anymore since 8-Jan-2007. + */ +static int do_led(xpd_t *xpd, lineno_t chan, byte which, bool on) +{ + int ret = 0; + struct FXS_priv_data *priv; + int value; + xbus_t *xbus; + + BUG_ON(!xpd); + BUG_ON(chan == ALL_LINES); + 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"); + value = BIT(2) | BIT(3); + value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_register_mask[which]); + if(on) + value |= led_register_vals[which]; + ret = SLIC_DIRECT_REQUEST(xbus, xpd, chan, SLIC_WRITE, 0x06, value); +out: + return ret; +} + +static void handle_fxs_leds(xpd_t *xpd) +{ + int i; + const enum fxs_leds colors[] = { LED_GREEN, LED_RED }; + int color; + unsigned int timer_count; + struct FXS_priv_data *priv; + + BUG_ON(!xpd); + priv = xpd->priv; + timer_count = xpd->timer_count; + for(color = 0; color < ARRAY_SIZE(colors); color++) { + 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)) { // Blinking + 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); + } + + } + } +} + +static void restore_leds(xpd_t *xpd) +{ + struct FXS_priv_data *priv; + int i; + + priv = xpd->priv; + for_each_line(xpd, i) { + if(IS_SET(xpd->offhook, i)) + MARK_ON(priv, i, LED_GREEN); + else + MARK_OFF(priv, i, LED_GREEN); + } +} + +#ifdef WITH_METERING +static int metering_gen(xpd_t *xpd, lineno_t chan, bool on) +{ + byte value = (on) ? 0x94 : 0x00; + + LINE_DBG(SIGNAL, xpd, chan, "METERING Generate: %s\n", (on)?"ON":"OFF"); + return SLIC_DIRECT_REQUEST(xpd->xbus, xpd, chan, SLIC_WRITE, 0x23, value); +} +#endif + +/*---------------- FXS: Methods -------------------------------------------*/ + +static xpd_t *FXS_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; + int regular_channels; + + if(subtype == 2) + regular_channels = min(6, CHANNELS_PERXPD); + else + regular_channels = min(8, CHANNELS_PERXPD); + channels = regular_channels; + if(unit == 0) + channels += 6; /* 2 DIGITAL OUTPUTS, 4 DIGITAL INPUTS */ + xpd = xpd_alloc(sizeof(struct FXS_priv_data), proto_table, channels); + if(!xpd) + return NULL; + if(unit == 0) { + XBUS_DBG(GENERAL, xbus, "First XPD detected. Initialize digital outputs/inputs\n"); + xpd->digital_outputs = BITMASK(LINES_DIGI_OUT) << regular_channels; + xpd->digital_inputs = BITMASK(LINES_DIGI_INP) << (regular_channels + LINES_DIGI_OUT); + } + xpd->direction = TO_PHONE; + xpd->revision = revision; + xpd->type_name = proto_table->name; + return xpd; +} + +static void clean_proc(xbus_t *xbus, xpd_t *xpd) +{ + struct FXS_priv_data *priv; + + BUG_ON(!xpd); + priv = xpd->priv; +#ifdef CONFIG_PROC_FS + if(priv->regfile) { + XPD_DBG(PROC, xpd, "Removing xpd SLIC file\n"); + priv->regfile->data = NULL; + remove_proc_entry(PROC_REGISTER_FNAME, xpd->proc_xpd_dir); + priv->regfile = 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->fxs_info) { + XPD_DBG(PROC, xpd, "Removing xpd FXS_INFO file\n"); + remove_proc_entry(PROC_FXS_INFO_FNAME, xpd->proc_xpd_dir); + priv->fxs_info = NULL; + } +#endif +} + +static int FXS_card_init(xbus_t *xbus, xpd_t *xpd) +{ + struct FXS_priv_data *priv; + int ret = 0; + int i; + + BUG_ON(!xpd); + priv = xpd->priv; +#ifdef CONFIG_PROC_FS + XPD_DBG(PROC, xpd, "Creating FXS_INFO file\n"); + priv->fxs_info = create_proc_read_entry(PROC_FXS_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_fxs_info_read, xpd); + if(!priv->fxs_info) { + XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_FXS_INFO_FNAME); + ret = -ENOENT; + goto err; + } + priv->fxs_info->owner = THIS_MODULE; +#ifdef WITH_METERING + XPD_DBG(PROC, xpd, "Creating Metering tone file\n"); + priv->meteringfile = create_proc_entry(PROC_METERING_FNAME, 0200, xpd->proc_xpd_dir); + 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; + priv->meteringfile->write_proc = proc_xpd_metering_write; + priv->meteringfile->read_proc = NULL; + priv->meteringfile->data = xpd; +#endif + XPD_DBG(PROC, xpd, "Creating SLICs 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 + for_each_line(xpd, i) { + priv->idletxhookstate[i] = FXS_LINE_POL_ACTIVE; + } + ret = run_initialize_registers(xpd); + if(ret < 0) + goto err; + /* + * Setup ring timers + */ + /* Software controled ringing (for CID) */ + ret = SLIC_DIRECT_REQUEST(xbus, xpd, ALL_CHANS, SLIC_WRITE, 0x22, 0x00); /* Ringing Oscilator Control */ + if(ret < 0) + goto err; + XPD_DBG(GENERAL, xpd, "done\n"); + for_each_line(xpd, i) { + do_led(xpd, i, LED_GREEN, 0); + do_led(xpd, i, LED_RED, 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); + } + restore_leds(xpd); + pcm_recompute(xpd, 0); + return 0; +err: + clean_proc(xbus, xpd); + XPD_ERR(xpd, "Failed initializing registers (%d)\n", ret); + return ret; +} + +static int FXS_card_remove(xbus_t *xbus, xpd_t *xpd) +{ + struct FXS_priv_data *priv; + + BUG_ON(!xpd); + priv = xpd->priv; + XPD_DBG(GENERAL, xpd, "\n"); + clean_proc(xbus, xpd); + return 0; +} + +static int FXS_card_zaptel_preregistration(xpd_t *xpd, bool on) +{ + xbus_t *xbus; + struct FXS_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 FXS channel %d\n", i); + if(IS_SET(xpd->digital_outputs, i)) { + snprintf(cur_chan->name, MAX_CHANNAME, "XPP_OUT/%02d/%1d%1d/%d", + xbus->num, xpd->addr.unit, xpd->addr.subunit, i); + } else if(IS_SET(xpd->digital_inputs, i)) { + snprintf(cur_chan->name, MAX_CHANNAME, "XPP_IN/%02d/%1d%1d/%d", + xbus->num, xpd->addr.unit, xpd->addr.subunit, i); + } else { + snprintf(cur_chan->name, MAX_CHANNAME, "XPP_FXS/%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 = FXS_DEFAULT_SIGCAP; + } + for_each_line(xpd, i) { + MARK_ON(priv, i, LED_GREEN); + msleep(4); + } + return 0; +} + +static int FXS_card_zaptel_postregistration(xpd_t *xpd, bool on) +{ + xbus_t *xbus; + struct FXS_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); + } + restore_leds(xpd); + return 0; +} + +/* + * Called with XPD spinlocked + */ +static void __do_mute_dtmf(xpd_t *xpd, int pos, bool muteit) +{ + LINE_DBG(SIGNAL, xpd, pos, "%s\n", (muteit) ? "MUTE" : "UNMUTE"); + if(muteit) + BIT_SET(xpd->mute_dtmf, pos); + else + BIT_CLR(xpd->mute_dtmf, pos); +} + +static int FXS_card_hooksig(xbus_t *xbus, xpd_t *xpd, int pos, zt_txsig_t txsig) +{ + struct FXS_priv_data *priv; + int ret = 0; + struct zt_chan *chan = NULL; + enum fxs_state txhook; + unsigned long flags; + + LINE_DBG(SIGNAL, xpd, pos, "%s\n", txsig2str(txsig)); + priv = xpd->priv; + BUG_ON(xpd->direction != TO_PHONE); + if (IS_SET(xpd->digital_inputs, pos)) { + LINE_DBG(SIGNAL, xpd, pos, "Ignoring signal sent to digital input line\n"); + return 0; + } + if(SPAN_REGISTERED(xpd)) + chan = &xpd->span.chans[pos]; + switch(txsig) { + case ZT_TXSIG_ONHOOK: + spin_lock_irqsave(&xpd->lock, flags); + xpd->ringing[pos] = 0; + BIT_CLR(xpd->cid_on, pos); + BIT_CLR(priv->search_fsk_pattern, pos); + BIT_CLR(priv->want_dtmf_events, pos); + BIT_CLR(priv->want_dtmf_mute, pos); + __do_mute_dtmf(xpd, pos, 0); + __pcm_recompute(xpd, 0); /* already spinlocked */ + spin_unlock_irqrestore(&xpd->lock, flags); + if(IS_SET(xpd->digital_outputs, pos)) { + LINE_DBG(SIGNAL, xpd, pos, "digital output OFF\n"); + ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 0); + return ret; + } + if (priv->lasttxhook[pos] == FXS_LINE_OPEN) { + /* + * Restore state after KEWL hangup. + */ + LINE_DBG(SIGNAL, xpd, pos, "KEWL STOP\n"); + linefeed_control(xbus, xpd, pos, FXS_LINE_POL_ACTIVE); + if(IS_SET(xpd->offhook, pos)) + MARK_ON(priv, pos, LED_GREEN); + } + ret = CALL_XMETHOD(RING, xbus, xpd, pos, 0); // RING off + if (!IS_SET(xpd->offhook, pos)) + start_stop_vm_led(xbus, xpd, pos); + txhook = priv->lasttxhook[pos]; + if(chan) { + switch(chan->sig) { + case ZT_SIG_EM: + case ZT_SIG_FXOKS: + case ZT_SIG_FXOLS: + txhook = priv->idletxhookstate[pos]; + break; + case ZT_SIG_FXOGS: + txhook = FXS_LINE_TIPOPEN; + break; + } + } + ret = linefeed_control(xbus, xpd, pos, txhook); + break; + case ZT_TXSIG_OFFHOOK: + txhook = priv->lasttxhook[pos]; + if(xpd->ringing[pos]) { + BIT_SET(xpd->cid_on, pos); + pcm_recompute(xpd, 0); + txhook = FXS_LINE_OHTRANS; + } + xpd->ringing[pos] = 0; + if(chan) { + switch(chan->sig) { + case ZT_SIG_EM: + txhook = FXS_LINE_POL_ACTIVE; + break; + default: + txhook = priv->idletxhookstate[pos]; + break; + } + } + ret = linefeed_control(xbus, xpd, pos, txhook); + break; + case ZT_TXSIG_START: + xpd->ringing[pos] = 1; + BIT_CLR(xpd->cid_on, pos); + BIT_CLR(priv->search_fsk_pattern, pos); + pcm_recompute(xpd, 0); + if(IS_SET(xpd->digital_outputs, pos)) { + LINE_DBG(SIGNAL, xpd, pos, "%s digital output ON\n", txsig2str(txsig)); + ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 1); + return ret; + } + ret = CALL_XMETHOD(RING, xbus, xpd, pos, 1); // RING on + break; + case ZT_TXSIG_KEWL: + LINE_DBG(SIGNAL, xpd, pos, "KEWL START\n"); + linefeed_control(xbus, xpd, pos, FXS_LINE_OPEN); + MARK_OFF(priv, pos, LED_GREEN); + break; + default: + XPD_NOTICE(xpd, "%s: Can't set tx state to %s (%d)\n", + __FUNCTION__, txsig2str(txsig), txsig); + ret = -EINVAL; + } + return ret; +} + +/* + * Private ioctl() + * We don't need it now, since we detect vmwi via FSK patterns + */ +static int FXS_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd, unsigned long arg) +{ + struct FXS_priv_data *priv; + xbus_t *xbus; + int val; + unsigned long flags; + + BUG_ON(!xpd); + priv = xpd->priv; + BUG_ON(!priv); + xbus = xpd->xbus; + BUG_ON(!xbus); + if(!TRANSPORT_RUNNING(xbus)) + return -ENODEV; + if (pos < 0 || pos >= xpd->channels) { + XPD_NOTICE(xpd, "Bad channel number %d in %s(), cmd=%u\n", + pos, __FUNCTION__, cmd); + return -EINVAL; + } + + switch (cmd) { + case ZT_ONHOOKTRANSFER: + if (get_user(val, (int __user *)arg)) + return -EFAULT; + LINE_DBG(SIGNAL, xpd, pos, "ZT_ONHOOKTRANSFER (%d millis)\n", val); + BUG_ON(pos == ALL_CHANS); + if (IS_SET(xpd->digital_inputs | xpd->digital_outputs, pos)) + return 0; /* Nothing to do */ + BIT_CLR(xpd->cid_on, pos); + if(priv->lasttxhook[pos] == FXS_LINE_POL_ACTIVE) { + priv->ohttimer[pos] = OHT_TIMER; + priv->idletxhookstate[pos] = FXS_LINE_POL_OHTRANS; + BIT_SET(priv->search_fsk_pattern, pos); + pcm_recompute(xpd, priv->search_fsk_pattern); + } + if(!IS_SET(xpd->offhook, pos)) + start_stop_vm_led(xbus, xpd, pos); + return 0; + case ZT_TONEDETECT: + if (get_user(val, (int __user *)arg)) + return -EFAULT; + LINE_DBG(SIGNAL, xpd, pos, "ZT_TONEDETECT: %s %s (dtmf_detection=%s)\n", + (val & ZT_TONEDETECT_ON) ? "ON" : "OFF", + (val & ZT_TONEDETECT_MUTE) ? "MUTE" : "NO-MUTE", + (dtmf_detection ? "YES" : "NO")); + if(!dtmf_detection) { + spin_lock_irqsave(&xpd->lock, flags); + if(IS_SET(priv->want_dtmf_events, pos)) { + /* Detection mode changed: Disable DTMF interrupts */ + SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x17, 0); + } + BIT_CLR(priv->want_dtmf_events, pos); + BIT_CLR(priv->want_dtmf_mute, pos); + __do_mute_dtmf(xpd, pos, 0); + __pcm_recompute(xpd, 0); /* already spinlocked */ + spin_unlock_irqrestore(&xpd->lock, flags); + return -ENOTTY; + } + /* + * During natively bridged calls, Asterisk + * will request one of the sides to stop sending + * dtmf events. Check the requested state. + */ + spin_lock_irqsave(&xpd->lock, flags); + if(val & ZT_TONEDETECT_ON) { + if(!IS_SET(priv->want_dtmf_events, pos)) { + /* Detection mode changed: Enable DTMF interrupts */ + LINE_DBG(SIGNAL, xpd, pos, + "ZT_TONEDETECT: Enable Hardware DTMF\n"); + SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x17, 1); + } + BIT_SET(priv->want_dtmf_events, pos); + } else { + if(IS_SET(priv->want_dtmf_events, pos)) { + /* Detection mode changed: Disable DTMF interrupts */ + LINE_DBG(SIGNAL, xpd, pos, + "ZT_TONEDETECT: Disable Hardware DTMF\n"); + SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x17, 0); + } + BIT_CLR(priv->want_dtmf_events, pos); + } + if(val & ZT_TONEDETECT_MUTE) { + BIT_SET(priv->want_dtmf_mute, pos); + } else { + BIT_CLR(priv->want_dtmf_mute, pos); + __do_mute_dtmf(xpd, pos, 0); + __pcm_recompute(xpd, 0); + } + spin_unlock_irqrestore(&xpd->lock, flags); + return 0; + case ZT_SETPOLARITY: + if (get_user(val, (int __user *)arg)) + return -EFAULT; + /* Can't change polarity while ringing or when open */ + if (priv->lasttxhook[pos] == FXS_LINE_RING || priv->lasttxhook[pos] == FXS_LINE_OPEN) { + LINE_ERR(xpd, pos, "ZT_SETPOLARITY: %s Cannot change when lasttxhook=0x%X\n", + (val)?"ON":"OFF", priv->lasttxhook[pos]); + return -EINVAL; + } + LINE_DBG(SIGNAL, xpd, pos, "ZT_SETPOLARITY: %s\n", (val)?"ON":"OFF"); + if ((val && !reversepolarity) || (!val && reversepolarity)) + priv->lasttxhook[pos] |= FXS_LINE_RING; + else + priv->lasttxhook[pos] &= ~FXS_LINE_RING; + linefeed_control(xbus, xpd, pos, priv->lasttxhook[pos]); + return 0; +#ifdef ZT_VMWI + case ZT_VMWI: /* message-waiting led control */ + if (get_user(val, (int __user *)arg)) + return -EFAULT; + if(!vmwi_ioctl) { + LINE_NOTICE(xpd, pos, "Got ZT_VMWI notification but vmwi_ioctl parameter is off. Ignoring.\n"); + return 0; + } + /* Digital inputs/outputs don't have VM leds */ + if (IS_SET(xpd->digital_inputs | xpd->digital_outputs, pos)) + return 0; + if (val) + BIT_SET(xpd->msg_waiting, pos); + else + BIT_CLR(xpd->msg_waiting, pos); + return 0; +#endif + default: + report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd); + } + return -ENOTTY; +} + +static int set_vm_led_mode(xbus_t *xbus, xpd_t *xpd, int pos, int on) +{ + int ret = 0; + BUG_ON(!xbus); + BUG_ON(!xpd); + + LINE_DBG(SIGNAL, xpd, pos, "%s%s\n", (on)?"ON":"OFF", (vmwineon)?"":" (Ignored)"); + if (!vmwineon) + return 0; + if (on) { + /* A write to register 0x40 will now turn on/off the VM led */ + ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x16, 0xE8, 0x03); + ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x15, 0xEF, 0x7B); + ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x14, 0x9F, 0x00); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x22, 0x19); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x4A, 0x34); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x30, 0xE0); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x31, 0x01); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x32, 0xF0); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x33, 0x05); + ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x1D, 0x00, 0x46); + } else { + /* A write to register 0x40 will now turn on/off the ringer */ + ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x16, 0x00, 0x00); + ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x15, 0x60, 0x01); + ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x14, 0xF0, 0x7E); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x22, 0x00); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x4A, 0x34); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x30, 0x00); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x31, 0x00); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x32, 0x00); + ret += SLIC_DIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x33, 0x00); + ret += SLIC_INDIRECT_REQUEST(xbus, xpd, pos, SLIC_WRITE, 0x1D, 0x00, 0x36); + } + + return (ret ? -EPROTO : 0); +} + +static void start_stop_vm_led(xbus_t *xbus, xpd_t *xpd, lineno_t pos) +{ + struct FXS_priv_data *priv; + bool on; + + BUG_ON(!xpd); + if (IS_SET(xpd->digital_outputs | xpd->digital_inputs, pos)) + return; + priv = xpd->priv; + on = IS_SET(xpd->msg_waiting, pos); + LINE_DBG(SIGNAL, xpd, pos, "%s\n", (on)?"ON":"OFF"); + set_vm_led_mode(xbus, xpd, pos, on); + do_chan_power(xbus, xpd, pos, on); + linefeed_control(xbus, xpd, pos, (on) ? FXS_LINE_RING : priv->idletxhookstate[pos]); +} + +static int FXS_card_open(xpd_t *xpd, lineno_t chan) +{ + struct FXS_priv_data *priv; + bool is_offhook; + + BUG_ON(!xpd); + priv = xpd->priv; + is_offhook = IS_SET(xpd->offhook, chan); + LINE_DBG(GENERAL, xpd, chan, "(is %shook)\n", (is_offhook)?"off":"on"); + /* + * Delegate updating zaptel to FXS_card_tick(): + * The problem is that zt_hooksig() is spinlocking the channel and + * we are called by zaptel with the spinlock already held on the + * same channel. + */ + BIT_SET(priv->update_offhook_state, chan); + return 0; +} + +static int FXS_card_close(xpd_t *xpd, lineno_t chan) +{ + struct FXS_priv_data *priv; + + BUG_ON(!xpd); + LINE_DBG(GENERAL, xpd, chan, "\n"); + priv = xpd->priv; + priv->idletxhookstate[chan] = FXS_LINE_POL_ACTIVE; + return 0; +} + +/* + * INPUT polling is done via SLIC register 0x06 (same as LEDS): + * 7 6 5 4 3 2 1 0 + * +-----+-----+-----+-----+-----+-----+-----+-----+ + * | I1 | I3 | | | I2 | I4 | | | + * +-----+-----+-----+-----+-----+-----+-----+-----+ + * + */ +static int input_channels[] = { 6, 7, 2, 3 }; // Slic numbers of input relays + +static void poll_inputs(xpd_t *xpd) +{ + int i; + + BUG_ON(xpd->xbus_idx != 0); // Only unit #0 has digital inputs + for(i = 0; i < ARRAY_SIZE(input_channels); i++) { + byte pos = input_channels[i]; + + SLIC_DIRECT_REQUEST(xpd->xbus, xpd, pos, SLIC_READ, 0x06, 0); + } +} + +static void handle_linefeed(xpd_t *xpd) +{ + struct FXS_priv_data *priv; + int i; + + BUG_ON(!xpd); + priv = xpd->priv; + BUG_ON(!priv); + for_each_line(xpd, i) { + if (priv->lasttxhook[i] == FXS_LINE_RING) { + /* RINGing, prepare for OHT */ + priv->ohttimer[i] = OHT_TIMER; + priv->idletxhookstate[i] = FXS_LINE_POL_OHTRANS; + } else { + if (priv->ohttimer[i]) { + priv->ohttimer[i]--; + if (!priv->ohttimer[i]) { + priv->idletxhookstate[i] = FXS_LINE_POL_ACTIVE; + if (priv->lasttxhook[i] == FXS_LINE_POL_OHTRANS) { + enum fxs_state txhook = FXS_LINE_POL_ACTIVE; + /* Apply the change if appropriate */ + BIT_CLR(xpd->cid_on, i); + BIT_CLR(priv->search_fsk_pattern, i); + pcm_recompute(xpd, 0); + linefeed_control(xpd->xbus, xpd, i, txhook); + } + } + } + } + } +} + +/* + * Optimized memcmp() like function. Only test for equality (true/false). + * This optimization reduced the detect_vmwi() runtime by a factor of 3. + */ +static inline bool mem_equal(const char a[], const char b[], size_t len) +{ + int i; + + for(i = 0; i < len; i++) + if(a[i] != b[i]) + return 0; + return 1; +} + +/* + * Detect Voice Mail Waiting Indication + */ +static void detect_vmwi(xpd_t *xpd) +{ + struct FXS_priv_data *priv; + xbus_t *xbus; + static const byte FSK_COMMON_PATTERN[] = { 0xA8, 0x49, 0x22, 0x3B, 0x9F, 0xFF, 0x1F, 0xBB }; + static const byte FSK_ON_PATTERN[] = { 0xA2, 0x2C, 0x1F, 0x2C, 0xBB, 0xA1, 0xA5, 0xFF }; + static const byte FSK_OFF_PATTERN[] = { 0xA2, 0x2C, 0x28, 0xA5, 0xB1, 0x21, 0x49, 0x9F }; + int i; + + BUG_ON(!xpd); + xbus = xpd->xbus; + priv = xpd->priv; + BUG_ON(!priv); + for_each_line(xpd, i) { + struct zt_chan *chan = &xpd->span.chans[i]; + byte *writechunk = chan->writechunk; + + if(IS_SET(xpd->offhook | xpd->cid_on | xpd->digital_inputs | xpd->digital_outputs, i)) + continue; +#if 0 + if(writechunk[0] != 0x7F && writechunk[0] != 0) { + int j; + + LINE_DBG(GENERAL, xpd, pos, "MSG:"); + for(j = 0; j < ZT_CHUNKSIZE; j++) { + if(print_dbg) + printk(" %02X", writechunk[j]); + } + if(print_dbg) + printk("\n"); + } +#endif + if(unlikely(mem_equal(writechunk, FSK_COMMON_PATTERN, ZT_CHUNKSIZE))) + BIT_SET(priv->found_fsk_pattern, i); + else if(unlikely(IS_SET(priv->found_fsk_pattern, i))) { + BIT_CLR(priv->found_fsk_pattern, i); + if(unlikely(mem_equal(writechunk, FSK_ON_PATTERN, ZT_CHUNKSIZE))) { + LINE_DBG(SIGNAL, xpd, i, "MSG WAITING ON\n"); + BIT_SET(xpd->msg_waiting, i); + start_stop_vm_led(xbus, xpd, i); + } else if(unlikely(mem_equal(writechunk, FSK_OFF_PATTERN, ZT_CHUNKSIZE))) { + LINE_DBG(SIGNAL, xpd, i, "MSG WAITING OFF\n"); + BIT_CLR(xpd->msg_waiting, i); + start_stop_vm_led(xbus, xpd, i); + } else { + int j; + + LINE_NOTICE(xpd, i, "MSG WAITING Unexpected:"); + for(j = 0; j < ZT_CHUNKSIZE; j++) { + printk(" %02X", writechunk[j]); + } + printk("\n"); + } + } + } +} + +static int FXS_card_tick(xbus_t *xbus, xpd_t *xpd) +{ + struct FXS_priv_data *priv; + + BUG_ON(!xpd); + priv = xpd->priv; + BUG_ON(!priv); +#if POLL_DIGITAL_INPUTS + if(poll_digital_inputs && xpd->xbus_idx == 0) { + if((xpd->timer_count % poll_digital_inputs) == 0) + poll_inputs(xpd); + } +#endif + handle_fxs_leds(xpd); + handle_linefeed(xpd); + if(priv->update_offhook_state) { /* set in FXS_card_open() */ + int i; + + for_each_line(xpd, i) { + if(!IS_SET(priv->update_offhook_state, i)) + continue; + /* + * Update LEDs and zaptel with current state of line. + */ + if(IS_SET(xpd->offhook, i)) { + LINE_NOTICE(xpd, i, "Already offhook during open. OK.\n"); + MARK_ON(priv, i, LED_GREEN); + update_line_status(xpd, i, 1); + } else { + MARK_OFF(priv, i, LED_GREEN); + update_line_status(xpd, i, 0); + } + BIT_CLR(priv->update_offhook_state, i); + } + } + if(SPAN_REGISTERED(xpd)) { + if(vmwineon && !vmwi_ioctl) + detect_vmwi(xpd); /* Detect via FSK modulation */ + } + return 0; +} + +/*---------------- FXS: HOST COMMANDS -------------------------------------*/ + +/* 0x0F */ HOSTCMD(FXS, XPD_STATE, bool on) +{ + int i; + enum fxs_state value = (on) ? FXS_LINE_POL_ACTIVE : FXS_LINE_OPEN; + unsigned long flags; + struct FXS_priv_data *priv; + + BUG_ON(!xbus); + BUG_ON(!xpd); + priv = xpd->priv; + spin_lock_irqsave(&xpd->lock, flags); + XPD_DBG(GENERAL, xpd, "%s\n", (on)?"on":"off"); + for_each_line(xpd, i) + linefeed_control(xbus, xpd, i, value); + if(on) { + MARK_ON(priv, ALL_CHANS, LED_GREEN); + } else { + MARK_OFF(priv, ALL_CHANS, LED_GREEN); + } + spin_unlock_irqrestore(&xpd->lock, flags); + return 0; +} + +/* 0x0F */ HOSTCMD(FXS, RING, lineno_t chan, bool on) +{ + int ret = 0; + struct FXS_priv_data *priv; + enum fxs_state value = (on) ? FXS_LINE_RING : FXS_LINE_POL_ACTIVE; + + BUG_ON(!xbus); + BUG_ON(!xpd); + LINE_DBG(SIGNAL, xpd, chan, "%s\n", (on)?"on":"off"); + priv = xpd->priv; + set_vm_led_mode(xbus, xpd, chan, 0); + do_chan_power(xbus, xpd, chan, on); // Power up (for ring) + ret = linefeed_control(xbus, xpd, chan, value); + if(on) { + MARK_BLINK(priv, chan, LED_GREEN, LED_BLINK_RING); + } else { + if(IS_BLINKING(priv, chan, LED_GREEN)) + MARK_BLINK(priv, chan, LED_GREEN, 0); + } + return ret; +} + +/* 0x0F */ HOSTCMD(FXS, RELAY_OUT, byte which, bool on) +{ + int value; + int relay_channels[] = { 0, 4 }; + + BUG_ON(!xbus); + BUG_ON(!xpd); + XPD_DBG(SIGNAL, xpd, "RELAY_OUT: which=%d -- %s\n", which, (on) ? "on" : "off"); + which = which % ARRAY_SIZE(relay_channels); + value = BIT(2) | BIT(3); + value |= ((BIT(5) | BIT(6) | BIT(7)) & ~led_register_mask[OUTPUT_RELAY]); + if(on) + value |= led_register_vals[OUTPUT_RELAY]; + return SLIC_DIRECT_REQUEST(xbus, xpd, relay_channels[which], SLIC_WRITE, 0x06, value); +} + +/*---------------- FXS: Astribank Reply Handlers --------------------------*/ + +HANDLER_DEF(FXS, SIG_CHANGED) +{ + xpp_line_t sig_status = RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_status); + xpp_line_t sig_toggles = RPACKET_FIELD(pack, FXS, SIG_CHANGED, sig_toggles); + struct FXS_priv_data *priv; + int i; + unsigned long flags; + + BUG_ON(!xpd); + BUG_ON(xpd->direction != TO_PHONE); + priv = xpd->priv; + XPD_DBG(SIGNAL, xpd, "(PHONE) sig_toggles=0x%04X sig_status=0x%04X\n", sig_toggles, sig_status); +#if 0 + Is this needed? + for_each_line(xpd, i) { + if(IS_SET(sig_toggles, i)) + do_chan_power(xpd->xbus, xpd, BIT(i), 0); // Power down (prevent overheating!!!) + } +#endif + spin_lock_irqsave(&xpd->lock, flags); + for_each_line(xpd, i) { + if(IS_SET(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i)) + continue; + if(IS_SET(sig_toggles, i)) { + xpd->ringing[i] = 0; /* No more ringing... */ +#ifdef WITH_METERING + metering_gen(xpd, i, 0); /* Stop metering... */ +#endif + MARK_BLINK(priv, i, LED_GREEN, 0); + if(IS_SET(sig_status, i)) { + LINE_DBG(SIGNAL, xpd, i, "OFFHOOK\n"); + MARK_ON(priv, i, LED_GREEN); + update_line_status(xpd, i, 1); + } else { + LINE_DBG(SIGNAL, xpd, i, "ONHOOK\n"); + MARK_OFF(priv, i, LED_GREEN); + update_line_status(xpd, i, 0); + } + } + } + __pcm_recompute(xpd, 0); /* in a spinlock */ + spin_unlock_irqrestore(&xpd->lock, flags); + return 0; +} + +static void process_digital_inputs(xpd_t *xpd, const reg_cmd_t *info) +{ + int i; + bool offhook = (REG_FIELD(info, data_low) & 0x1) == 0; + xpp_line_t lines = BIT(REG_FIELD(info, chipsel)); + + /* Map SLIC number into line number */ + for(i = 0; i < ARRAY_SIZE(input_channels); i++) { + int channo = input_channels[i]; + int newchanno; + + if(IS_SET(lines, channo)) { + newchanno = xpd->channels - LINES_DIGI_INP + i; + BIT_CLR(lines, channo); + BIT_SET(lines, newchanno); + xpd->ringing[newchanno] = 0; // Stop ringing. No leds for digital inputs. + if(offhook && !IS_SET(xpd->offhook, newchanno)) { // OFFHOOK + LINE_DBG(SIGNAL, xpd, newchanno, "OFFHOOK\n"); + update_line_status(xpd, newchanno, 1); + } else if(!offhook && IS_SET(xpd->offhook, newchanno)) { // ONHOOK + LINE_DBG(SIGNAL, xpd, newchanno, "ONHOOK\n"); + update_line_status(xpd, newchanno, 0); + } + } + } +} + +static const char dtmf_digits[] = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '*', '#', 'A', 'B', 'C', 'D' +}; + +/* + * This function is called with spinlocked XPD + */ +static void process_dtmf(xpd_t *xpd, xpp_line_t lines, byte val) +{ + int i; + byte digit; + bool is_down = val & 0x10; + struct FXS_priv_data *priv; + + if(!dtmf_detection) + return; + priv = xpd->priv; + val &= 0xF; + if(val <= 0) { + if(is_down) + XPD_NOTICE(xpd, "Bad DTMF value %d. Ignored\n", val); + return; + } + val--; + digit = dtmf_digits[val]; + for_each_line(xpd, i) { + if(IS_SET(lines, i)) { + int event = (is_down) ? ZT_EVENT_DTMFDOWN : ZT_EVENT_DTMFUP; + bool want_mute = IS_SET(priv->want_dtmf_mute, i); + bool want_event = IS_SET(priv->want_dtmf_events, i); + + if(want_event) { + LINE_DBG(SIGNAL, xpd, i, + "DTMF digit %s (val=%d) '%c' (want_mute=%s)\n", + (is_down)?"DOWN":"UP", val, digit, + (want_mute) ? "yes" : "no"); + } else { + LINE_DBG(SIGNAL, xpd, i, + "Ignored DTMF digit %s '%c'\n", + (is_down)?"DOWN":"UP", digit); + } + /* + * FIXME: we currently don't use the want_dtmf_mute until + * we are sure about the logic in Asterisk native bridging. + * Meanwhile, simply mute it on button press. + */ + if(is_down && want_mute) + __do_mute_dtmf(xpd, i, 1); + else + __do_mute_dtmf(xpd, i, 0); + __pcm_recompute(xpd, 0); /* XPD is locked */ + if(want_event) + zt_qevent_lock(&xpd->chans[i], event | digit); + break; + } + } +} + +static int FXS_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info) +{ + unsigned long flags; + struct FXS_priv_data *priv; + byte regnum; + bool indirect; + + spin_lock_irqsave(&xpd->lock, flags); + priv = xpd->priv; + BUG_ON(!priv); + indirect = (REG_FIELD(info, regnum) == 0x1E); + regnum = (indirect) ? REG_FIELD(info, subreg) : REG_FIELD(info, regnum); + XPD_DBG(REGS, xpd, "%s reg_num=0x%X, dataL=0x%X dataH=0x%X\n", + (indirect)?"I":"D", + regnum, REG_FIELD(info, data_low), REG_FIELD(info, data_high)); + if(!SPAN_REGISTERED(xpd)) + goto out; + /* + * Process digital inputs polling results + */ + if(xpd->xbus_idx == 0 && !indirect && regnum == 0x06) + process_digital_inputs(xpd, info); + if(!indirect && regnum == SLIC_REG_DTMF) { + byte val = REG_FIELD(info, data_low); + xpp_line_t lines = BIT(REG_FIELD(info, chipsel)); + +#if 0 + XPD_DBG(SIGNAL, xpd, "DTMF result lines=0x%04X val=%d\n", + lines, val); +#endif + process_dtmf(xpd, lines, val); + } +out: + /* 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; + } + spin_unlock_irqrestore(&xpd->lock, flags); + return 0; +} + +static xproto_table_t PROTO_TABLE(FXS) = { + .owner = THIS_MODULE, + .entries = { + /* Prototable Card Opcode */ + XENTRY( FXS, FXS, SIG_CHANGED ), + }, + .name = "FXS", + .type = XPD_TYPE_FXS, + .xops = { + .card_new = FXS_card_new, + .card_init = FXS_card_init, + .card_remove = FXS_card_remove, + .card_zaptel_preregistration = FXS_card_zaptel_preregistration, + .card_zaptel_postregistration = FXS_card_zaptel_postregistration, + .card_hooksig = FXS_card_hooksig, + .card_tick = FXS_card_tick, + .card_pcm_fromspan = generic_card_pcm_fromspan, + .card_pcm_tospan = generic_card_pcm_tospan, + .card_open = FXS_card_open, + .card_close = FXS_card_close, + .card_ioctl = FXS_card_ioctl, + .card_register_reply = FXS_card_register_reply, + + .RING = XPROTO_CALLER(FXS, RING), + .RELAY_OUT = XPROTO_CALLER(FXS, RELAY_OUT), + .XPD_STATE = XPROTO_CALLER(FXS, XPD_STATE), + }, + .packet_is_valid = fxs_packet_is_valid, + .packet_dump = fxs_packet_dump, +}; + +static bool fxs_packet_is_valid(xpacket_t *pack) +{ + const xproto_entry_t *xe; + + // DBG(GENERAL, "\n"); + xe = xproto_card_entry(&PROTO_TABLE(FXS), XPACKET_OP(pack)); + return xe != NULL; +} + +static void fxs_packet_dump(const char *msg, xpacket_t *pack) +{ + DBG(GENERAL, "%s\n", msg); +} + +/*------------------------- SLIC Handling --------------------------*/ + +static int proc_fxs_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 FXS_priv_data *priv; + int i; + int led; + + if(!xpd) + return -ENODEV; + spin_lock_irqsave(&xpd->lock, flags); + priv = xpd->priv; + BUG_ON(!priv); + len += sprintf(page + len, "%-8s %-10s %-10s %-10s\n", + "Channel", + "idletxhookstate", + "lasttxhook", + "ohttimer" + ); + for_each_line(xpd, i) { + char pref; + + if(IS_SET(xpd->digital_outputs, i)) + pref = 'O'; + else if(IS_SET(xpd->digital_inputs, i)) + pref = 'I'; + else + pref = ' '; + len += sprintf(page + len, "%c%7d %10d %10d %10d\n", + pref, + i, + priv->idletxhookstate[i], + priv->lasttxhook[i], + priv->ohttimer[i] + ); + } + len += sprintf(page + len, "\n"); + for(led = 0; led < NUM_LEDS; led++) { + len += sprintf(page + len, "LED #%d", led); + 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, "%d ", IS_SET(priv->ledstate[led], 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, "%d ", IS_SET(priv->ledcontrol[led], i)); + } + len += sprintf(page + len, "\n\t%-17s: ", "led_counter"); + for_each_line(xpd, i) { + if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) + len += sprintf(page + len, "%d ", LED_COUNTER(priv,i,led)); + } + 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; + unsigned data_high = 0; + char op; /* [W]rite, [R]ead */ + char reg_type; /* [D]irect, [I]ndirect */ + int reg_num; + int elements; + bool writing; + char *p; + reg_cmd_t regcmd; + xbus_t *xbus; + int ret = -EINVAL; + + 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; + if(!XBUS_GET(xbus)) { + XBUS_DBG(GENERAL, xbus, "Dropped packet. Is shutting down.\n"); + return -EBUSY; + } + elements = sscanf(cmdline, "%d %c%c %x %x %x", + &chipsel, + &op, ®_type, ®_num, + &data_low, + &data_high); + XPD_DBG(REGS, xpd, "'%s': %d %c%c %02X %02X %02X\n", cmdline, chipsel, op, reg_type, reg_num, data_low, data_high); + if(elements < 4) { // At least: chipsel, op, reg_type, reg_num + ERR("Not enough arguments: (%d args) '%s'\n", elements, cmdline); + goto out; + } + if(!VALID_CHIPSEL(chipsel)) { + ERR("Bad chipsel number: %d\n", chipsel); + goto out; + } + REG_FIELD(®cmd, chipsel) = chipsel; + switch(op) { + case 'W': + writing = 1; + break; + case 'R': + writing = 0; + break; + default: + ERR("Unkown operation type '%c'\n", op); + goto out; + } + switch(reg_type) { + case 'I': + REG_FIELD(®cmd, do_subreg) = 1; + REG_FIELD(®cmd, regnum) = 0x1E; // FIXME: card dependent... + REG_FIELD(®cmd, subreg) = reg_num; + break; + case 'D': + REG_FIELD(®cmd, do_subreg) = 0; + REG_FIELD(®cmd, regnum) = reg_num; + REG_FIELD(®cmd, subreg) = 0; + break; + default: + ERR("Unkown register type '%c'\n", reg_type); + goto out; + } + if( + (op == 'W' && reg_type == 'D' && elements != 5) || + (op == 'W' && reg_type == 'I' && elements != 6) || + (op == 'R' && reg_type == 'D' && elements != 4) || + (op == 'R' && reg_type == 'I' && elements != 4) + ) { + ERR("%s: '%s' (%d elements): %d %c%c %02X %02X %02X\n", __FUNCTION__, + cmdline, elements, + chipsel, op, reg_type, reg_num, data_low, data_high); + goto out; + } + regcmd.bytes = sizeof(regcmd) - 1; + REG_FIELD(®cmd, data_low) = data_low; + REG_FIELD(®cmd, data_high) = data_high; + REG_FIELD(®cmd, read_request) = writing; + xpd->requested_reply = regcmd; + if(print_dbg) + dump_reg_cmd("FXS", ®cmd, 1); + ret = xpp_register_request(xpd->xbus, xpd, + REG_FIELD(®cmd, chipsel), + writing, + REG_FIELD(®cmd, do_subreg), + REG_FIELD(®cmd, regnum), + REG_FIELD(®cmd, subreg), + REG_FIELD(®cmd, data_low), + REG_FIELD(®cmd, data_high)); +out: + XBUS_PUT(xbus); + 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; + bool indirect; + + if(!xpd) + return -ENODEV; + spin_lock_irqsave(&xpd->lock, flags); + info = &xpd->last_reply; + indirect = (REG_FIELD(info, regnum) == 0x1E); + regnum = (indirect) ? REG_FIELD(info, subreg) : 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 DH\n"); + len += sprintf(page + len, "%2d\tR%c\t%02X\t%02X %02X\n", + REG_FIELD(info, chipsel), + (indirect)?'I':'D', + regnum, REG_FIELD(info, data_low), REG_FIELD(info, data_high)); + 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_write(struct file *file, const char __user *buffer, unsigned long count, void *data) +{ + xpd_t *xpd = data; + char buf[MAX_PROC_WRITE]; + lineno_t chan; + int num; + int ret; + + if(!xpd) + return -ENODEV; + if(count >= MAX_PROC_WRITE - 1) { + XPD_ERR(xpd, "Metering string too long (%lu)\n", count); + return -EINVAL; + } + if(copy_from_user(&buf, buffer, count)) + return -EFAULT; + buf[count] = '\0'; + ret = sscanf(buf, "%d", &num); + if(ret != 1) { + XPD_ERR(xpd, "Metering value should be number. Got '%s'\n", buf); + return -EINVAL; + } + chan = num; + if(chan != ALL_CHANS && chan > xpd->channels) { + XPD_ERR(xpd, "Metering tone: bad channel number %d\n", chan); + return -EINVAL; + } + if((ret = metering_gen(xpd, chan, 1)) < 0) { + XPD_ERR(xpd, "Failed sending metering tone\n"); + return ret; + } + return count; +} +#endif + +int __init card_fxs_startup(void) +{ + INFO("revision %s\n", XPP_VERSION); +#ifdef POLL_DIGITAL_INPUTS + INFO("FEATURE: with DIGITAL INPUTS support (polled every %d msec)\n", + poll_digital_inputs); +#else + INFO("FEATURE: without DIGITAL INPUTS support\n"); +#endif +#ifdef ZT_VMWI + INFO("FEATURE: ZT_VMWI\n"); +#else + INFO("FEATURE: NO ZT_VMWI\n"); +#endif +#ifdef WITH_METERING + INFO("FEATURE: WITH METERING Generation\n"); +#else + INFO("FEATURE: NO METERING Generation\n"); +#endif + xproto_register(&PROTO_TABLE(FXS)); + return 0; +} + +void __exit card_fxs_cleanup(void) +{ + xproto_unregister(&PROTO_TABLE(FXS)); +} + +MODULE_DESCRIPTION("XPP FXS Card Driver"); +MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(XPP_VERSION); +MODULE_ALIAS_XPD(XPD_TYPE_FXS); + +module_init(card_fxs_startup); +module_exit(card_fxs_cleanup); |