diff options
author | tzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2008-02-04 23:00:48 +0000 |
---|---|---|
committer | tzafrir <tzafrir@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2008-02-04 23:00:48 +0000 |
commit | 7e068801fbf82413ac0a5e63e586c268bd457434 (patch) | |
tree | 9b61e9a4e07167e0b7d347e4336245724befa29c /kernel/xpp/card_fxo.c | |
parent | 29daeebad888269fa0ee2ca7e54e238c8498ca2d (diff) |
Move kernel stuff to under kernel/
(merged branch /zaptel/team/tzafrir/move )
Closes issue #7117.
git-svn-id: http://svn.digium.com/svn/zaptel/branches/1.4@3793 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'kernel/xpp/card_fxo.c')
-rw-r--r-- | kernel/xpp/card_fxo.c | 1218 |
1 files changed, 1218 insertions, 0 deletions
diff --git a/kernel/xpp/card_fxo.c b/kernel/xpp/card_fxo.c new file mode 100644 index 0000000..cbe953b --- /dev/null +++ b/kernel/xpp/card_fxo.c @@ -0,0 +1,1218 @@ +/* + * 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"); +DEF_PARM(uint, poll_battery_interval, 500, 0644, "Poll battery interval in milliseconds (0 - disable)"); +DEF_PARM(uint, poll_power_denial_interval, 40, 0644, "Power denial detection poll 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 POLREV_THRESHOLD 1000 /* minimum duration for polarity reversal detection (in ticks) */ +#define BAT_THRESHOLD 3 +#define BAT_DEBOUNCE 1000 /* compensate for battery voltage fluctuation (in ticks) */ + +/* Shortcuts */ +#define DAA_WRITE 1 +#define DAA_READ 0 +#define DAA_DIRECT_REQUEST(xbus,xpd,chipsel,writing,reg,dL) \ + xpp_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_REG_RING 0x05 +#define DAA_REG_METERING 0x11 /* 17 */ +#define DAA_REG_CURRENT 0x1C /* 28 */ +#define DAA_REG_VBAT 0x1D /* 29 */ + +#define POWER_DENIAL_CURRENT 3 +#define POWER_DENIAL_TIME 80 /* 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; + signed char battery_voltage[CHANNELS_PERXPD]; + xpp_line_t battery; + ushort battery_debounce[CHANNELS_PERXPD]; + xpp_line_t polarity; + ushort polarity_counter[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); +} + +static 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, 0); + /* + * 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 */ + /* + * We don't want to check battery during ringing + * due to voltage fluctuations. + */ + priv->battery_debounce[pos] = 0; + 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_REG_RING, value); + if(to_offhook) { + BIT_SET(xpd->offhook, pos); + } 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_REG_METERING, 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, 0); + 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; +} + +static 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, 0); + 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_REG_VBAT, 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_REG_CURRENT, 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_REG_METERING, 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); + if(poll_power_denial_interval != 0 && (priv->poll_counter % poll_power_denial_interval) == 0) + 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 <wctdm.h> +/* + * 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); + if(!TRANSPORT_RUNNING(xpd->xbus)) + return -ENODEV; + 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; + case ZT_TONEDETECT: + /* + * Asterisk call all span types with this (FXS specific) + * call. Silently ignore it. + */ + LINE_DBG(GENERAL, xpd, pos, + "ZT_TONEDETECT (FXO: NOTIMPLEMENTED)\n"); + return -ENOTTY; + default: + report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd); + return -ENOTTY; + } + return 0; +} + +/*---------------- FXO: HOST COMMANDS -------------------------------------*/ + +/* 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; +} + +/* 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); +} + +/* 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, XPACKET_ADDR(pack), 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 false ring (only %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; +} + +#ifndef ZT_GET_PARAMS_V1 +#define zt_alarm_channel(a,b) zt_qevent_lock(a,( (b)==ZT_ALARM_NONE )? \ + ZT_EVENT_NOALARM : ZT_EVENT_ALARM) +#endif +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); + int msec; + + + priv = xpd->priv; + BUG_ON(!priv); + priv->battery_voltage[chipsel] = data_low; + if(bat < BAT_THRESHOLD) { + /* + * Check for battery voltage fluctuations + */ + if(IS_SET(priv->battery, chipsel)) { + int milliseconds; + + milliseconds = priv->battery_debounce[chipsel]++ * + poll_battery_interval; + if(milliseconds > BAT_DEBOUNCE) { + LINE_DBG(SIGNAL, xpd, chipsel, "BATTERY OFF voltage=%d\n", bat); + BIT_CLR(priv->battery, chipsel); + if(SPAN_REGISTERED(xpd)) + zt_alarm_channel(&xpd->chans[chipsel], ZT_ALARM_RED); + } + + } + } 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_alarm_channel(&xpd->chans[chipsel], ZT_ALARM_NONE); + } + } + /* + * Handle reverse polarity + */ + if(IS_SET(priv->polarity, chipsel) == pol) { + /* same, same, nothing to see here, move on */ + priv->polarity_counter[chipsel] = 0; + return; + } + /* + * Track polarity reversals and debounce spikes. + * Only reversals with long duration count. + */ + msec = priv->polarity_counter[chipsel]++ * poll_battery_interval; + if (msec >= POLREV_THRESHOLD) { + LINE_DBG(SIGNAL, xpd, chipsel, "Polarity is %s\n", + (pol)?"Positive":"Negative"); + priv->polarity_counter[chipsel] = 0; + if (pol) + BIT_SET(priv->polarity, chipsel); + else + BIT_CLR(priv->polarity, chipsel); + /* polarity reversal during offhook should be reported to zaptel */ + if(IS_SET(xpd->offhook, chipsel)) { + /* Inform Zaptel */ + LINE_DBG(SIGNAL, 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, 0); +#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, 0); + } + } 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_REG_METERING, data_low); + } +} +#endif + +static int FXO_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info) +{ + struct FXO_priv_data *priv; + lineno_t chipsel; + + priv = xpd->priv; + BUG_ON(!priv); + chipsel = REG_FIELD(info, chipsel); + switch(REG_FIELD(info, regnum)) { + case DAA_REG_VBAT: + update_battery_status(xpd, REG_FIELD(info, data_low), chipsel); + break; + case DAA_REG_CURRENT: + update_power_denial(xpd, REG_FIELD(info, data_low), chipsel); + break; +#ifdef WITH_METERING + case DAA_REG_METERING: + update_metering_state(xpd, REG_FIELD(info, data_low), chipsel); + break; +#endif + } + LINE_DBG(REGS, xpd, chipsel, "%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; +} + + +static xproto_table_t PROTO_TABLE(FXO) = { + .owner = THIS_MODULE, + .entries = { + /* Prototable Card Opcode */ + XENTRY( FXO, FXO, SIG_CHANGED ), + }, + .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, + .card_register_reply = FXO_card_register_reply, + + .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), XPACKET_OP(pack)); + 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, "%3d ", 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, "%3d ", 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, "%3d ", IS_BLINKING(priv,i,LED_GREEN)); + } + len += sprintf(page + len, "\n\t%-17s: ", "battery"); + for_each_line(xpd, i) { + len += sprintf(page + len, "%3d ", IS_SET(priv->battery, i)); + } + len += sprintf(page + len, "\n\t%-17s: ", "polarity"); + for_each_line(xpd, i) { + len += sprintf(page + len, "%3d ", IS_SET(priv->polarity, i)); + } + len += sprintf(page + len, "\n\t%-17s: ", "polarity_counter"); + for_each_line(xpd, i) { + len += sprintf(page + len, "%3d ", priv->polarity_counter[i]); + } + len += sprintf(page + len, "\n\t%-17s: ", "battery_voltage"); + for_each_line(xpd, i) { + len += sprintf(page + len, "%3d ", priv->battery_voltage[i]); + } +#ifdef WITH_METERING + len += sprintf(page + len, "\n\t%-17s: ", "metering"); + for_each_line(xpd, i) { + len += sprintf(page + len, "%3d ", 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 = -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", + &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); + goto out; + } + if(!VALID_CHIPSEL(chipsel)) { + ERR("Bad chipsel number: %d\n", chipsel); + goto out; + } + 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); + goto out; + } + 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); + goto out; + } + 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); + goto out; + } + regcmd.bytes = sizeof(regcmd) - 1; + REG_FIELD(®cmd, data_low) = data_low; + REG_FIELD(®cmd, data_high) = 0; + REG_FIELD(®cmd, read_request) = writing; + 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)); +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; + + 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 <oron@actcom.co.il>"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(XPP_VERSION); +MODULE_ALIAS_XPD(XPD_TYPE_FXO); + +module_init(card_fxo_startup); +module_exit(card_fxo_cleanup); |