diff options
Diffstat (limited to 'xpp/card_fxo.c')
-rw-r--r-- | xpp/card_fxo.c | 946 |
1 files changed, 946 insertions, 0 deletions
diff --git a/xpp/card_fxo.c b/xpp/card_fxo.c new file mode 100644 index 0000000..d383e5d --- /dev/null +++ b/xpp/card_fxo.c @@ -0,0 +1,946 @@ +/* + * 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 <version.h> /* For zaptel version */ +#include "xpd.h" +#include "xproto.h" +#include "xpp_zap.h" +#include "card_fxo.h" +#include "zap_debug.h" + +static const char rcsid[] = "$Id$"; + +DEF_PARM(int, print_dbg, 0, "Print DBG statements"); /* must be before zap_debug.h */ +DEF_PARM(uint, poll_battery_interval, 100, "Poll battery interval in milliseconds"); +DEF_PARM(bool, report_battery, 0, "Report battery status to zaptel"); + +/* Signaling is opposite (fxs signalling for fxo card) */ +#if 1 +#define FXO_DEFAULT_SIGCAP (ZT_SIG_FXSKS | ZT_SIG_FXSLS) +#else +#define FXO_DEFAULT_SIGCAP (ZT_SIG_SF) +#endif + +enum fxo_leds { + LED_GREEN, +}; + +#define NUM_LEDS 1 +#define DELAY_UNTIL_DIALTONE 3000 + +/*---------------- FXO Protocol Commands ----------------------------------*/ + +static /* 0x0F */ DECLARE_CMD(FXO, CHAN_ENABLE, xpp_line_t lines, bool on); +static /* 0x0F */ DECLARE_CMD(FXO, CHAN_CID, int pos); +static /* 0x0F */ DECLARE_CMD(FXO, RING, int pos, bool on); +static /* 0x0F */ DECLARE_CMD(FXO, SETHOOK, int pos, bool offhook); +static /* 0x0F */ DECLARE_CMD(FXO, RELAY_OUT, byte which, bool on); +static /* 0x0F */ DECLARE_CMD(FXO, DAA_QUERY, int pos, byte reg_num); + +static bool fxo_packet_is_valid(xpacket_t *pack); +static void fxo_packet_dump(xpacket_t *pack); +static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data); +static int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data); +static int proc_xpd_slic_write(struct file *file, const char __user *buffer, unsigned long count, void *data); +static int process_slic_cmdline(xpd_t *xpd, char *cmdline); + +#define PROC_DAA_FNAME "slics" +#define PROC_FXO_INFO_FNAME "fxo_info" + +struct FXO_priv_data { + struct proc_dir_entry *xpd_slic; + struct proc_dir_entry *fxo_info; + slic_reply_t requested_reply; + slic_reply_t last_reply; + xpp_line_t battery; + xpp_line_t ledstate[NUM_LEDS]; /* 0 - OFF, 1 - ON */ + int blinking[NUM_LEDS][CHANNELS_PERXPD]; +}; + +/*---------------- FXO: Static functions ----------------------------------*/ + +#define IS_BLINKING(priv,pos,color) ((priv)->blinking[color][pos] != 0) +#define DO_BLINK(priv,pos,color,val) ((priv)->blinking[color][pos] = (val)) + +/* + * LED control is done via DAA register 0x20 + */ +static int do_led(xpd_t *xpd, lineno_t pos, byte which, bool on) +{ + int ret = 0; + xpacket_t *pack; + slic_cmd_t *sc; + int len; + struct FXO_priv_data *priv; + xpp_line_t lines; + xbus_t *xbus; + + BUG_ON(!xpd); + xbus = xpd->xbus; + priv = xpd->priv; + which = which % NUM_LEDS; + if(IS_SET(xpd->digital_outputs, pos) || IS_SET(xpd->digital_inputs, pos)) + goto out; + if(pos == ALL_LINES) { + lines = ~0; + priv->ledstate[which] = (on) ? ~0 : 0; + } else { + lines = BIT(pos); + if(on) { + BIT_SET(priv->ledstate[which], pos); + } else { + BIT_CLR(priv->ledstate[which], pos); + } + } + if(!lines) // Nothing to do + goto out; + DBG("%s/%s: LED: lines=0x%04X which=%d -- %s\n", xbus->busname, xpd->xpdname, lines, which, (on) ? "on" : "off"); + XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); + sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd); + len = slic_cmd_direct_write(sc, lines, 0x20, on); + // DBG("LED pack: line=%d %s\n", i, (on)?"on":"off"); + pack->datalen = len; + packet_send(xbus, pack); +out: + return ret; +} + +static void handle_fxo_leds(xpd_t *xpd) +{ + int i; + unsigned long flags; + const enum fxo_leds color = LED_GREEN; + unsigned int timer_count; + struct FXO_priv_data *priv; + + BUG_ON(!xpd); + spin_lock_irqsave(&xpd->lock, flags); + priv = xpd->priv; + timer_count = xpd->timer_count; + for_each_line(xpd, i) { + if(IS_SET(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i)) + continue; + if(IS_BLINKING(priv,i,color)) { + // led state is toggled + if((timer_count % LED_BLINK_PERIOD) == 0) { + DBG("%s/%s/%d: led_state=%s\n", xpd->xbus->busname, xpd->xpdname, i, + (IS_SET(priv->ledstate[color], i))?"ON":"OFF"); + if(!IS_SET(priv->ledstate[color], i)) { + do_led(xpd, i, color, 1); + } else { + do_led(xpd, i, color, 0); + } + } + } + } + spin_unlock_irqrestore(&xpd->lock, flags); +} + +static void do_sethook(xpd_t *xpd, int pos, bool offhook) +{ + unsigned long flags; + struct FXO_priv_data *priv; + + BUG_ON(!xpd); + BUG_ON(xpd->direction == TO_PHONE); // We can SETHOOK state only on PSTN + priv = xpd->priv; + BUG_ON(!priv); + if(!IS_SET(priv->battery, pos)) { + DBG("%s/%s/%d: WARNING: called while battery is off\n", xpd->xbus->busname, xpd->xpdname, pos); + } + spin_lock_irqsave(&xpd->lock, flags); + xpd->ringing[pos] = 0; // No more rings + CALL_XMETHOD(SETHOOK, xpd->xbus, xpd, pos, offhook); + if(offhook) { + BIT_SET(xpd->hookstate, pos); + } else { + BIT_CLR(xpd->hookstate, pos); + xpd->delay_until_dialtone[pos] = 0; + } + spin_unlock_irqrestore(&xpd->lock, flags); + if(offhook) + wake_up_interruptible(&xpd->txstateq[pos]); +} + +/*---------------- FXO: Methods -------------------------------------------*/ + +static xpd_t *FXO_card_new(xbus_t *xbus, int xpd_num, const xproto_table_t *proto_table, byte revision) +{ + xpd_t *xpd = NULL; + int channels = min(8, CHANNELS_PERXPD); + + xpd = xpd_alloc(sizeof(struct FXO_priv_data), xbus, xpd_num, proto_table, channels, revision); + if(!xpd) + return NULL; + xpd->direction = TO_PSTN; + xpd->revision = revision; + return xpd; +} + +static void clean_proc(xbus_t *xbus, xpd_t *xpd) +{ + struct FXO_priv_data *priv; + + BUG_ON(!xpd); + priv = xpd->priv; + DBG("%s/%s\n", xbus->busname, xpd->xpdname); +#ifdef CONFIG_PROC_FS + if(priv->xpd_slic) { + DBG("Removing xpd DAA file %s/%s\n", xbus->busname, xpd->xpdname); + remove_proc_entry(PROC_DAA_FNAME, xpd->proc_xpd_dir); + } + if(priv->fxo_info) { + DBG("Removing xpd FXO_INFO file %s/%s\n", xbus->busname, xpd->xpdname); + remove_proc_entry(PROC_FXO_INFO_FNAME, xpd->proc_xpd_dir); + } +#endif +} + +static int FXO_card_init(xbus_t *xbus, xpd_t *xpd) +{ + struct FXO_priv_data *priv; + int ret = 0; + int i; + + BUG_ON(!xpd); + priv = xpd->priv; +#ifdef CONFIG_PROC_FS + DBG("Creating FXO_INFO file for %s/%s\n", xbus->busname, xpd->xpdname); + priv->fxo_info = create_proc_read_entry(PROC_FXO_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_fxo_info_read, xpd); + if(!priv->fxo_info) { + ERR("Failed to create proc '%s' for %s/%s\n", PROC_FXO_INFO_FNAME, xbus->busname, xpd->xpdname); + ret = -ENOENT; + goto err; + } + priv->fxo_info->owner = THIS_MODULE; + DBG("Creating DAAs file for %s/%s\n", xbus->busname, xpd->xpdname); + priv->xpd_slic = create_proc_entry(PROC_DAA_FNAME, 0644, xpd->proc_xpd_dir); + if(!priv->xpd_slic) { + ERR("Failed to create proc file for DAAs of %s/%s\n", xbus->busname, xpd->xpdname); + ret = -ENOENT; + goto err; + } + priv->xpd_slic->owner = THIS_MODULE; + priv->xpd_slic->write_proc = proc_xpd_slic_write; + priv->xpd_slic->read_proc = proc_xpd_slic_read; + priv->xpd_slic->data = xpd; +#endif + ret = run_initialize_registers(xpd); + if(ret < 0) + goto err; + // Hanghup all lines + for_each_line(xpd, i) { + init_waitqueue_head(&xpd->txstateq[i]); + do_sethook(xpd, i, 0); + } + DBG("done: %s/%s\n", xbus->busname, xpd->xpdname); + return 0; +err: + clean_proc(xbus, xpd); + ERR("%s/%s: Failed initializing registers (%d)\n", xbus->busname, xpd->xpdname, ret); + return ret; +} + +static int FXO_card_remove(xbus_t *xbus, xpd_t *xpd) +{ + struct FXO_priv_data *priv; + + BUG_ON(!xpd); + priv = xpd->priv; + DBG("%s/%s\n", xbus->busname, xpd->xpdname); + clean_proc(xbus, xpd); + return 0; +} + +static int FXO_card_zaptel_preregistration(xpd_t *xpd, bool on) +{ + xbus_t *xbus; + struct FXO_priv_data *priv; + int i; + unsigned long flags; + + BUG_ON(!xpd); + xbus = xpd->xbus; + BUG_ON(!xbus); + priv = xpd->priv; + BUG_ON(!priv); + DBG("%s/%s (%d)\n", xbus->busname, xpd->xpdname, on); + snprintf(xpd->span.desc, MAX_SPANDESC, "Xorcom XPD #%d/%d: FXO", xbus->num, xpd->id); + for_each_line(xpd, i) { + struct zt_chan *cur_chan = &xpd->chans[i]; + + DBG("setting FXO channel %d\n", i); + snprintf(cur_chan->name, MAX_CHANNAME, "XPP_FXO/%d/%d/%d", xbus->num, xpd->id, i); + cur_chan->chanpos = i + 1; + cur_chan->pvt = xpd; + cur_chan->sigcap = FXO_DEFAULT_SIGCAP; + } + spin_lock_irqsave(&xpd->lock, flags); + do_led(xpd, ALL_LINES, LED_GREEN, LED_OFF); + spin_unlock_irqrestore(&xpd->lock, flags); + for_each_line(xpd, i) { + do_led(xpd, i, LED_GREEN, LED_ON); + mdelay(50); + } + return 0; +} + +static int FXO_card_zaptel_postregistration(xpd_t *xpd, bool on) +{ + xbus_t *xbus; + struct FXO_priv_data *priv; + int i; + + BUG_ON(!xpd); + xbus = xpd->xbus; + BUG_ON(!xbus); + priv = xpd->priv; + BUG_ON(!priv); + DBG("%s/%s (%d)\n", xbus->busname, xpd->xpdname, on); + for_each_line(xpd, i) { + do_led(xpd, i, LED_GREEN, LED_OFF); + mdelay(50); + } + return 0; +} + +#ifdef WITH_RBS +int FXO_card_hooksig(xbus_t *xbus, xpd_t *xpd, int pos, zt_txsig_t txsig) +{ + struct FXO_priv_data *priv; + + priv = xpd->priv; + BUG_ON(!priv); + DBG("%s/%s/%d: %s\n", xbus->busname, xpd->xpdname, pos, txsig2str(txsig)); + BUG_ON(xpd->direction != TO_PSTN); + /* XXX Enable hooksig for FXO XXX */ + switch(txsig) { + case ZT_TXSIG_START: + break; + case ZT_TXSIG_OFFHOOK: + do_sethook(xpd, pos, 1); + break; + case ZT_TXSIG_ONHOOK: + do_sethook(xpd, pos, 0); + break; + default: + NOTICE("Can't set tx state to %s (%d)\n", txsig2str(txsig), txsig); + return -EINVAL; + } + return 0; +} + +#else +int FXO_card_sethook(xbus_t *xbus, xpd_t *xpd, int pos, int hookstate) +{ + int ret = 0; + struct FXO_priv_data *priv; + + DBG("%s/%s/%d: %s\n", xbus->busname, xpd->xpdname, pos, hookstate2str(hookstate)); + switch(hookstate) { + /* On-hook, off-hook: The PBX is playing a phone on an FXO line. */ + case ZT_ONHOOK: + do_sethook(xpd, pos, 0); + break; + case ZT_START: + DBG("%s/%s/%d: fall through ZT_OFFHOOK\n", xbus->busname, xpd->xpdname, pos); + xpd->delay_until_dialtone[pos] = DELAY_UNTIL_DIALTONE; + // Fall through + case ZT_OFFHOOK: + do_sethook(xpd, pos, 1); + wait_event_interruptible(xpd->txstateq[pos], xpd->delay_until_dialtone[pos] <= 0); + break; + case ZT_WINK: + WARN("No code yet\n"); + break; + case ZT_FLASH: + WARN("No code yet\n"); + break; + case ZT_RING: + DBG("%s/%s/%d: ZT_RING: %d\n", xbus->busname, xpd->xpdname, pos, xpd->ringing[pos]); + break; + case ZT_RINGOFF: + WARN("No code yet\n"); + break; + } + return ret; +} +#endif + +static void poll_battery(xbus_t *xbus, xpd_t *xpd) +{ + int i; + + for_each_line(xpd, i) { + CALL_PROTO(FXO, DAA_QUERY, xbus, xpd, i, DAA_VBAT_REGISTER); + } +} + + +static int FXO_card_tick(xbus_t *xbus, xpd_t *xpd) +{ + static unsigned rate_limit = 0; + struct FXO_priv_data *priv; + + BUG_ON(!xpd); + priv = xpd->priv; + BUG_ON(!priv); + rate_limit++; + if(poll_battery_interval != 0 && (rate_limit % poll_battery_interval) == 0) { + poll_battery(xbus, xpd); + } + handle_fxo_leds(xpd); + return 0; +} + +/* FIXME: based on data from from wctdm.h */ +#include <wctdm.h> +static const int echotune_reg[] = {30,45,46,47,58,49,50,51,52}; +union echotune { + /* "coeff 0" is acim */ + unsigned char coeff[sizeof(echotune_reg)]; + struct wctdm_echo_coefs wctdm_struct; +}; + +static int FXO_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd, unsigned long arg) +{ + union echotune echoregs; + int i,ret; + + BUG_ON(!xpd); + DBG("cmd: 0x%x, expecting: 0x%x, pos=%d.\n", cmd, WCTDM_SET_ECHOTUNE, pos); + switch (cmd) { + case WCTDM_SET_ECHOTUNE: + DBG("-- Setting echo registers: \n"); + /* first off: check if this span is fxs. If not: -EINVALID */ + if (copy_from_user(&echoregs.wctdm_struct, + (struct wctdm_echo_coefs*)arg, sizeof(echoregs.wctdm_struct))) + return -EFAULT; + + /* Set the ACIM register */ + /* quick and dirty registers writing: */ + for (i=0; i<sizeof(echotune_reg); i++) { + char buf[22]; + xpp_line_t lines = BIT(pos); + sprintf(buf, "%02X %02X %02X %02X WD %2X %2X", + (lines & 0xFF), + ((lines >> 8) & 0xFF), + ((lines >> 16) & 0xFF), + ((lines >> 24) & 0xFF), + echotune_reg[i],echoregs.coeff[i] + ); + /* FIXME: code duplicated from proc_xpd_register_write */ + ret = process_slic_cmdline(xpd, buf); + if(ret < 0) + return ret; + mdelay(1); + } + + DBG("-- Set echo registers successfully\n"); + + break; + default: + return -ENOTTY; + } + return 0; +} + +/*---------------- FXO: HOST COMMANDS -------------------------------------*/ + +static /* 0x0F */ HOSTCMD(FXO, CHAN_ENABLE, xpp_line_t lines, bool on) +{ + unsigned long flags; + int ret = 0; + int i; + + BUG_ON(!xbus); + BUG_ON(!xpd); + if(!lines) { + return 0; + } + DBG("Channel Activation: 0x%4X %s\n", lines, (on) ? "on" : "off"); + if(on) { + for_each_line(xpd, i) { + spin_lock_irqsave(&xpd->lock, flags); + do_led(xpd, i, LED_GREEN, LED_ON); + spin_unlock_irqrestore(&xpd->lock, flags); + mdelay(20); + } + for_each_line(xpd, i) { + spin_lock_irqsave(&xpd->lock, flags); + do_led(xpd, i, LED_GREEN, LED_OFF); + spin_unlock_irqrestore(&xpd->lock, flags); + mdelay(20); + } + } + return ret; +} + +static /* 0x0F */ HOSTCMD(FXO, CHAN_CID, int pos) +{ + int ret = 0; + xpp_line_t lines = BIT(pos); + + BUG_ON(!xbus); + BUG_ON(!xpd); + if(!lines) { + return 0; + } + DBG("%s/%s/%d:\n", xbus->busname, xpd->xpdname, pos); + return ret; +} + + +static /* 0x0F */ HOSTCMD(FXO, RING, int pos, bool on) +{ + int ret = 0; + xpacket_t *pack; + slic_cmd_t *sc; + xpp_line_t mask = BIT(pos); + int len; + + BUG_ON(!xbus); + BUG_ON(!xpd); + if(!mask) { + return 0; + } + DBG("%s/%s/%d %s\n", xpd->xbus->busname, xpd->xpdname, pos, (on) ? "on" : "off"); + XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); + sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd); + len = slic_cmd_direct_write(sc, mask, 0x40, (on)?0x04:0x01); + pack->datalen = len; + + packet_send(xbus, pack); + return ret; +} + +static /* 0x0F */ HOSTCMD(FXO, SETHOOK, int pos, bool offhook) +{ + int ret = 0; + xpacket_t *pack; + slic_cmd_t *sc; + int len; + unsigned long flags; + bool value; + + BUG_ON(!xbus); + BUG_ON(!xpd); + value = (offhook) ? 0x09 : 0x08; + // value |= BIT(3); /* Bit 3 is for CID */ + DBG("%s/%s/%d: SETHOOK: value=0x%02X %s\n", xbus->busname, xpd->xpdname, pos, value, (offhook)?"OFFHOOK":"ONHOOK"); + spin_lock_irqsave(&xpd->lock, flags); + XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); + sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd); + len = slic_cmd_direct_write(sc, BIT(pos), 0x05, value); + pack->datalen = len; + packet_send(xbus, pack); + do_led(xpd, pos, LED_GREEN, (offhook)?LED_ON:LED_OFF); + spin_unlock_irqrestore(&xpd->lock, flags); + return ret; +} + +static /* 0x0F */ HOSTCMD(FXO, RELAY_OUT, byte which, bool on) +{ + return -ENOSYS; +} + +static /* 0x0F */ HOSTCMD(FXO, DAA_QUERY, int pos, byte reg_num) +{ + int ret = 0; + xpacket_t *pack; + slic_cmd_t *sc; + int len; + + BUG_ON(!xbus); + BUG_ON(!xpd); + // DBG("\n"); + XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); + sc = &RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd); + len = slic_cmd_direct_read(sc, BIT(pos), reg_num); + + pack->datalen = len; + + packet_send(xbus, pack); + return ret; +} + +/*---------------- FXO: Astribank Reply Handlers --------------------------*/ + +HANDLER_DEF(FXO, SIG_CHANGED) +{ + xpp_line_t sig_status = RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_status); + xpp_line_t sig_toggles = RPACKET_FIELD(pack, FXO, SIG_CHANGED, sig_toggles); + unsigned long flags; + int i; + + if(!xpd) { + NOTICE("%s: received %s for non-existing xpd: %d\n", + __FUNCTION__, cmd->name, XPD_NUM(pack->content.addr)); + return -EPROTO; + } + DBG("%s/%s: (PSTN) sig_toggles=0x%04X sig_status=0x%04X\n", xpd->xbus->busname, xpd->xpdname, sig_toggles, sig_status); + spin_lock_irqsave(&xpd->lock, flags); + for_each_line(xpd, i) { + if(IS_SET(sig_status, i)) { + xpd->ringing[i] = 1; + } else { + xpd->ringing[i] = 0; + } + } + spin_unlock_irqrestore(&xpd->lock, flags); + return 0; +} + +HANDLER_DEF(FXO, DAA_REPLY) +{ + slic_reply_t *info = &RPACKET_FIELD(pack, FXO, DAA_REPLY, info); + xpp_line_t lines = RPACKET_FIELD(pack, FXO, DAA_REPLY, lines); + unsigned long flags; + struct FXO_priv_data *priv; + + if(!xpd) { + NOTICE("%s: received %s for non-existing xpd: %d\n", + __FUNCTION__, cmd->name, XPD_NUM(pack->content.addr)); + return -EPROTO; + } + spin_lock_irqsave(&xpd->lock, flags); + priv = xpd->priv; + BUG_ON(!priv); + if(!info->indirect && info->reg_num == DAA_VBAT_REGISTER) { + xpp_line_t last_batt_on = priv->battery; + xpp_line_t changed_lines; + int i; + + if(abs(info->data_low) < BAT_THRESHOLD) { + priv->battery &= ~lines; + // DBG("%s/%s: BATTERY OFF (%04X) = %d\n", xpd->xbus->busname, xpd->xpdname, lines, info->data_low); + } else { + priv->battery |= lines; + // DBG("%s/%s: BATTERY ON (%04X) = %d\n", xpd->xbus->busname, xpd->xpdname, lines, info->data_low); + } + changed_lines = last_batt_on ^ priv->battery; + for_each_line(xpd, i) { + if(IS_SET(changed_lines, i)) { + update_line_status(xpd, i, IS_SET(priv->battery, i)); + } + } + } +#if 0 + DBG("DAA_REPLY: xpd #%d %s reg_num=0x%X, dataL=0x%X dataH=0x%X\n", + xpd->id, (info->indirect)?"I":"D", + info->reg_num, info->data_low, info->data_high); +#endif + + /* Update /proc info only if reply relate to the last slic read request */ + if(priv->requested_reply.indirect == info->indirect && + priv->requested_reply.reg_num == info->reg_num) { + priv->last_reply = *info; + } + spin_unlock_irqrestore(&xpd->lock, flags); + return 0; +} + + +xproto_table_t PROTO_TABLE(FXO) = { + .owner = THIS_MODULE, + .entries = { + /* Card Opcode */ + XENTRY( FXO, SIG_CHANGED ), + XENTRY( FXO, DAA_REPLY ), + }, + .name = "FXO", + .type = XPD_TYPE_FXO, + .xops = { + .card_new = FXO_card_new, + .card_init = FXO_card_init, + .card_remove = FXO_card_remove, + .card_zaptel_preregistration = FXO_card_zaptel_preregistration, + .card_zaptel_postregistration = FXO_card_zaptel_postregistration, +#ifdef WITH_RBS + .card_hooksig = FXO_card_hooksig, +#else + .card_sethook = FXO_card_sethook, +#endif + .card_tick = FXO_card_tick, + .card_ioctl = FXO_card_ioctl, + + .RING = XPROTO_CALLER(FXO, RING), + .SETHOOK = XPROTO_CALLER(FXO, SETHOOK), + .RELAY_OUT = XPROTO_CALLER(FXO, RELAY_OUT), + .CHAN_ENABLE = XPROTO_CALLER(FXO, CHAN_ENABLE), + .CHAN_CID = XPROTO_CALLER(FXO, CHAN_CID), + + .SYNC_SOURCE = XPROTO_CALLER(GLOBAL, SYNC_SOURCE), + .PCM_WRITE = XPROTO_CALLER(GLOBAL, PCM_WRITE), + }, + .packet_is_valid = fxo_packet_is_valid, + .packet_dump = fxo_packet_dump, +}; + +static bool fxo_packet_is_valid(xpacket_t *pack) +{ + const xproto_entry_t *xe; + + //DBG("\n"); + xe = xproto_card_entry(&PROTO_TABLE(FXO), pack->content.opcode); + return xe != NULL; +} + +static void fxo_packet_dump(xpacket_t *pack) +{ + DBG("\n"); +} + +/*------------------------- DAA Handling --------------------------*/ + +static int proc_fxo_info_read(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + unsigned long flags; + xpd_t *xpd = data; + struct FXO_priv_data *priv; + int i; + + if(!xpd) + return -ENODEV; + spin_lock_irqsave(&xpd->lock, flags); + priv = xpd->priv; + BUG_ON(!priv); + len += sprintf(page + len, "\t%-17s: ", "Channel"); + for_each_line(xpd, i) { + if(!IS_SET(xpd->digital_outputs, i) && !IS_SET(xpd->digital_inputs, i)) + len += sprintf(page + len, "%d ", 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, "%d ", 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, "%d ", IS_BLINKING(priv,i,LED_GREEN)); + } + len += sprintf(page + len, "\n\t%-17s: ", "battery"); + for_each_line(xpd, i) { + len += sprintf(page + len, "%d ", IS_SET(priv->battery, i)); + } + len += sprintf(page + len, "\n"); + spin_unlock_irqrestore(&xpd->lock, flags); + if (len <= off+count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + + +static int proc_xpd_slic_read(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + unsigned long flags; + xpd_t *xpd = data; + slic_reply_t *info; + struct FXO_priv_data *priv; + + BUG_ON(!xpd); + spin_lock_irqsave(&xpd->lock, flags); + priv = xpd->priv; + BUG_ON(!priv); + info = &priv->last_reply; + len += sprintf(page + len, "# Writing bad data into this file may damage your hardware!\n"); + len += sprintf(page + len, "# Consult firmware docs first\n"); + len += sprintf(page + len, "DAA_REPLY: %s reg_num=0x%X, dataH=0x%X dataL=0x%X\n", + (info->indirect)?"I":"D", + info->reg_num, info->data_high, info->data_low); + spin_unlock_irqrestore(&xpd->lock, flags); + if (len <= off+count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + +/* + * Direct/Indirect + * v + * FF FF FF FF WD 06 1 + * ^---------^ ^ Reg + * | Write/Read + * | + * DAA # + */ +static int parse_slic_cmd(const char *buf, slic_cmd_t *sc, slic_reply_t *requested_reply) +{ + char op; /* [W]rite, [R]ead */ + char reg_type; /* [D]irect, [I]ndirect */ + int s1, s2, s3, s4; + int reg_num; + int data_low, data_high; + xpp_line_t lines; + int ret; + + ret = sscanf(buf, "%x %x %x %x %c%c %x %x %x", + &s1, &s2, &s3, &s4, &op, ®_type, ®_num, &data_high, &data_low); + lines = (s4 << 24) | (s3 << 16) | (s2 << 8) | (s1); + switch(op) { + case 'R': + if(reg_type == 'D' && ret == 7) { + // DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num); + ret = slic_cmd_direct_read(sc, lines, reg_num); + if(requested_reply) { + requested_reply->indirect = 0; + requested_reply->reg_num = reg_num; + } + } else if(reg_type == 'I' && ret == 7) { + // DBG("0x%X 0x%X 0x%X 0x%X %c %x\n", s1, s2, s3, s4, reg_type, reg_num); + ret = slic_cmd_indirect_read(sc, lines, reg_num); + if(requested_reply) { + requested_reply->indirect = 1; + requested_reply->reg_num = reg_num; + } + } else { + NOTICE("%s: Bad read input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type); + goto err; + } + break; + case 'W': + if(reg_type == 'D' && ret == 8) { + // DBG("0x%X 0x%X 0x%X 0x%X %c %x %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high); + ret = slic_cmd_direct_write(sc, lines, reg_num, data_high); + } else if(reg_type == 'I' && ret == 9) { + // DBG("0x%X 0x%X 0x%X 0x%X %c %x %X %X\n", s1, s2, s3, s4, reg_type, reg_num, data_high, data_low); + ret = slic_cmd_indirect_write(sc, lines, reg_num, data_low, data_high); + } else { + NOTICE("%s: Bad write input: ret=%d buf='%s' reg_type=%c\n", __FUNCTION__, ret, buf, reg_type); + goto err; + } + break; + default: + NOTICE("%s: Bad input: ret=%d buf='%s' op=%c\n", __FUNCTION__, ret, buf, op); + goto err; + } + return ret; +err: + return -EINVAL; +} + +static int process_slic_cmdline(xpd_t *xpd, char *cmdline) +{ + xbus_t *xbus; + struct FXO_priv_data *priv; + slic_cmd_t sc; + xpacket_t *pack; + char *p; + int len = strlen(cmdline); + + BUG_ON(!xpd); + xbus = xpd->xbus; + priv = xpd->priv; + if((p = strchr(cmdline, '#')) != NULL) /* Truncate comments */ + *p = '\0'; + if((p = strchr(cmdline, ';')) != NULL) /* Truncate comments */ + *p = '\0'; + for(p = cmdline; *p && (*p == ' ' || *p == '\t'); p++) /* Trim leading whitespace */ + ; + if(*p == '\0') + return 0; + len = parse_slic_cmd(p, &sc, &priv->requested_reply); + if(len < 0) + return len; + if(!sc.lines) { + NOTICE("%s: no channels are marked. Skip.\n", __FUNCTION__); + return 0; + } + dump_slic_cmd("WRITE_DAA", &sc); + XPACKET_NEW(pack, xbus, FXO, DAA_WRITE, xpd->id); + RPACKET_FIELD(pack, FXO, DAA_WRITE, slic_cmd) = sc; + pack->datalen = len; + packet_send(xbus, pack); + return 0; +} + +static int proc_xpd_slic_write(struct file *file, const char __user *buffer, unsigned long count, void *data) +{ + xpd_t *xpd = data; + const int LINE_LEN = 500; + char buf[LINE_LEN]; + char *p; + int i; + int ret; + + if(!xpd) + return -ENODEV; + for(i = 0; i < count; /* noop */) { + for(p = buf; p < buf + LINE_LEN; 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 + LINE_LEN) + return -E2BIG; + *p = '\0'; + ret = process_slic_cmdline(xpd, buf); + if(ret < 0) + return ret; + mdelay(1); + } + return count; +} + + +int __init card_fxo_startup(void) +{ + INFO("%s revision %s\n", THIS_MODULE->name, ZAPTEL_VERSION); + 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(ZAPTEL_VERSION); +MODULE_ALIAS_XPD(XPD_TYPE_FXO); + +module_init(card_fxo_startup); +module_exit(card_fxo_cleanup); |